Skip to content

Releases: jamesrochabrun/SwiftAnthropic

SwiftAnthropic v2.1.1

25 Feb 00:27
a297d39
Compare
Choose a tag to compare
Screenshot 2025-02-24 at 4 25 50 PM

Extended Thinking

Claude 3.7 Sonnet offers enhanced reasoning capabilities with extended thinking mode. This feature allows Claude to perform step-by-step reasoning before providing a final answer, improving response quality for complex tasks.

Extended Thinking Parameters

public struct Thinking: Encodable {
    /// The type of thinking, currently only "enabled" is supported
    let type: ThinkingType
    /// Token budget allocated for extended thinking (maximum number of tokens to use for thinking)
    let budgetTokens: Int
    
    public enum ThinkingType: String, Encodable {
        case enabled
    }
    
    private enum CodingKeys: String, CodingKey {
        case type
        case budgetTokens = "budget_tokens"
    }
    
    public init(type: ThinkingType = .enabled, budgetTokens: Int) {
        self.type = type
        self.budgetTokens = budgetTokens
    }
}

Basic Non-Streaming Example

// Create a message with thinking enabled
let userMessage = MessageParameter.Message(
    role: .user,
    content: .text("What would happen if we doubled the Earth's gravity overnight?")
)

// Enable thinking with a budget of 16,000 tokens
let parameters = MessageParameter(
    model: .claude37Sonnet,
    messages: [userMessage],
    maxTokens: 4000,
    thinking: .init(budgetTokens: 16000)
)

// Make API call
let response = try await service.createMessage(parameters)

// Process the response
var thinkingContent = ""
var responseText = ""

for content in response.content {
    switch content {
    case .text(let text, _):
        responseText = text
        print("Final answer: \(text)")
    case .thinking(let thinking):
        thinkingContent = thinking.thinking
        print("Thinking process: \(thinkingContent)")
    case .redactedThinking(_):
        print("Some thinking was redacted for safety reasons")
    default:
        break
    }
}

Multi-Turn Conversation (Non-Streaming)

For multi-turn conversations, include the thinking blocks as part of the assistant message in the conversation history:

// Conversation history
var messages: [MessageParameter.Message] = []

// First user message
let firstUserMessage = MessageParameter.Message(
    role: .user,
    content: .text("What would happen if we doubled the Earth's gravity overnight?")
)
messages.append(firstUserMessage)

// Create parameters with thinking enabled
let parameters = MessageParameter(
    model: .claude37Sonnet,
    messages: messages,
    maxTokens: 4000,
    thinking: .init(budgetTokens: 16000)
)

// Make API call
let response = try await service.createMessage(parameters)

// Process the response and extract thinking blocks and text
var thinkingBlocks: [MessageParameter.Message.Content.ContentObject] = []
var responseText = ""

for content in response.content {
    switch content {
    case .text(let text, _):
        responseText = text
    case .thinking(let thinking):
        if let signature = thinking.signature {
            thinkingBlocks.append(.thinking(thinking.thinking, signature))
        }
    case .redactedThinking(let data):
        thinkingBlocks.append(.redactedThinking(data))
    default:
        break
    }
}

// Create all content objects for the assistant message
var contentObjects = thinkingBlocks
contentObjects.append(.text(responseText))

// Save assistant's response to conversation history with thinking blocks included
messages.append(MessageParameter.Message(
    role: .assistant,
    content: .list(contentObjects)
))

// For the next turn, add the user's new message
let secondUserPrompt = "How would this affect human evolution over the next thousand years?"
messages.append(MessageParameter.Message(
    role: .user,
    content: .text(secondUserPrompt)
))

// Create new parameters using the existing messages array
let nextParameters = MessageParameter(
    model: .claude37Sonnet,
    messages: messages,
    maxTokens: 4000,
    thinking: .init(budgetTokens: 16000)
)

// Continue the conversation...
let nextResponse = try await service.createMessage(nextParameters)

// Process the next response...

Streaming with Extended Thinking

For streaming responses with thinking enabled, you can use the provided StreamHandler or implement your own solution:

// StreamHandler to collect thinking blocks from the stream
let streamHandler = StreamHandler()

// Conversation history
var messages: [MessageParameter.Message] = []

// Add user message
let userMessage = MessageParameter.Message(
    role: .user,
    content: .text("Solve this math problem: If a train travels at 120 km/h, how long will it take to cover 450 km?")
)
messages.append(userMessage)

// Create parameters with thinking enabled and streaming
let parameters = MessageParameter(
    model: .claude37Sonnet,
    messages: messages,
    maxTokens: 4000,
    stream: true,
    thinking: .init(budgetTokens: 8000)
)

// Start streaming
let stream = try await service.streamMessage(parameters)

// Process stream events
for try await event in stream {
    // Let the handler process the event
    streamHandler.handleStreamEvent(event)
    
    // Update UI as needed
    if let delta = event.delta {
        switch delta.type {
        case "thinking_delta":
            if let thinking = delta.thinking {
                print("Thinking: \(thinking)", terminator: "")
            }
        case "text_delta":
            if let text = delta.text {
                print("Response: \(text)", terminator: "")
            }
        default:
            break
        }
    }
}

// After streaming is complete, get the final text response
let finalResponse = streamHandler.textResponse

// Get thinking blocks
let thinkingBlocks = streamHandler.getThinkingBlocksForAPI()

// Create all content objects for the assistant message
var contentObjects = thinkingBlocks
contentObjects.append(.text(finalResponse))

// Save assistant's response to conversation history with thinking blocks included
messages.append(MessageParameter.Message(
    role: .assistant,
    content: .list(contentObjects)
))

// For the next turn, add the user's new message
let secondUserPrompt = "Can you explain the solution in more detail?"
messages.append(MessageParameter.Message(
    role: .user,
    content: .text(secondUserPrompt)
))

// Continue the conversation with the next request
// (Using the same pattern)

StreamHandler Implementation

The StreamHandler is a utility class that helps collect thinking blocks and their signatures from streaming events. Note that this is a convenience implementation - engineers can create their own implementation as the API supports the necessary endpoints and provides decoders:

public final class StreamHandler {
   
    public init() {}
    
    // Current thinking content being collected
    private var currentThinking = ""
    // Current signature being collected
    private var signature: String?
    // Current text response being collected
    private var currentResponse = ""
    
    // Track the current active content block index and type
    private var currentBlockIndex: Int?
    private var currentBlockType: String?
    
    // Store all collected thinking blocks
    private var thinkingBlocks: [(thinking: String, signature: String?)] = []
    // Stored redacted thinking blocks
    private var redactedThinkingBlocks: [String] = []
    
    // Process a stream event
    public func handleStreamEvent(_ event: MessageStreamResponse) {
        switch event.streamEvent {
        case .contentBlockStart:
            handleContentBlockStart(event)
        case .contentBlockDelta:
            handleContentBlockDelta(event)
        case .contentBlockStop:
            handleContentBlockStop()
        case .messageStart, .messageDelta, .messageStop, .none:
            break
        }
    }
    
    // Get the thinking blocks for use in subsequent API calls
    public func getThinkingBlocksForAPI() -> [MessageParameter.Message.Content.ContentObject] {
        var blocks: [MessageParameter.Message.Content.ContentObject] = []
        
        // Add regular thinking blocks
        for block in thinkingBlocks {
            if let signature = block.signature {
                blocks.append(.thinking(block.thinking, signature))
            }
        }
        
        // Add redacted thinking blocks if any
        for data in redactedThinkingBlocks {
            blocks.append(.redactedThinking(data))
        }
        
        return blocks
    }
    
    // Get text response content
    public var textResponse: String {
        return currentResponse
    }
    
    // Reset all stored data
    public func reset() {
        currentThinking = ""
        signature = nil
        currentResponse = ""
        currentBlockIndex = nil
        currentBlockType = nil
        thinkingBlocks.removeAll()
        redactedThinkingBlocks.removeAll()
    }
    
    // Private implementation details...
    private func handleContentBlockStart(_ event: MessageStreamResponse) {
        guard let contentBlock = event.contentBlock, let index = event.index else { return }
        
        currentBlockIndex = index
        currentBlockType = contentBlock.type
        
        switch contentBlock.type {
        case "thinking":
            currentThinking = contentBlock.thinking ?? ""
        case "redacted_thinking":
            if let data = contentBlock.data {
                redactedThinkingBlocks.append(data)
            }
        case "text":
            currentRes...
Read more

SwiftAnthropic v2.1.0

23 Jan 23:18
2bfd7ca
Compare
Choose a tag to compare

Adding Citations support in the Swift Library

Screenshot 2025-01-23 at 3 17 12 PM

Usage

let maxTokens = 1024
let prompt = "Please analyze this document"

// Load PDF data
let pdfData = // your PDF data
let base64PDF = pdfData.base64EncodedString()

// Create document source
let documentSource = try MessageParameter.Message.Content.DocumentSource.pdf(base64Data: base64PDF, citations: .init(enabled: true))

// Create message with document and prompt
let message = MessageParameter.Message(
    role: .user,
    content: .list([
        .document(documentSource),
        .text(prompt)
    ])
)

// Create parameters
let parameters = MessageParameter(
    model: .claude35Sonnet,
    messages: [message],
    maxTokens: maxTokens
)

// Send request
let response = try await service.streamMessage(parameters)

SwiftAnthropic v2.0.0

04 Jan 05:34
bdf0a66
Compare
Choose a tag to compare

What's Changed

Count Tokens Support:

Count Tokens

Parameters:

public struct MessageTokenCountParameter: Encodable {
    /// The model that will complete your prompt.
    /// See [models](https://docs.anthropic.com/claude/reference/selecting-a-model) for additional details and options.
    public let model: String

    /// Input messages.
    /// Our models are trained to operate on alternating user and assistant conversational turns.
    /// Each input message must be an object with a role and content.
    public let messages: [MessageParameter.Message]

    /// System prompt.
    /// A system prompt is a way of providing context and instructions to Claude.
    /// System role can be either a simple String or an array of objects, use the objects array for prompt caching.
    public let system: MessageParameter.System?

    /// Tools that can be used in the messages
    public let tools: [MessageParameter.Tool]?
}

Response:

public struct MessageInputTokens: Decodable {

   /// The total number of tokens across the provided list of messages, system prompt, and tools.
   public let inputTokens: Int
}

Usage:

let messageParameter = MessageParameter.Message(role: .user, content: .text("Hello, Claude"))
let parameters = MessageTokenCountParameter(
    model: .claude3Sonnet,
    messages: [messageParameter]
)
let tokenCount = try await service.countTokens(parameter: parameters)
print("Input tokens: \(tokenCount.inputTokens)")

Also:

New Contributors

Full Changelog: v1.8.6...v2.0.0

SwiftAnthropic v1.8.6

28 Dec 09:26
Compare
Choose a tag to compare

PDF support
Process PDFs with Claude 3.5 Sonnet. Extract text, analyze charts, and understand visual content from your documents.

You can now ask Claude bout any text, pictures, charts, and tables in PDFs you provide. Some sample use cases:

Analyzing financial reports and understanding charts/tables
Extracting key information from legal documents
Translation assistance for documents
Converting document information into structured formats

PDF Support

Claude can now analyze PDFs through the Messages API. Here's a simple example:

let maxTokens = 1024
let prompt = "Please analyze this document"

// Load PDF data
let pdfData = // your PDF data
let base64PDF = pdfData.base64EncodedString()

// Create document source
let documentSource = try MessageParameter.Message.Content.DocumentSource(data: base64PDF)

// Create message with document and prompt
let message = MessageParameter.Message(
    role: .user,
    content: .list([
        .document(documentSource),
        .text(prompt)
    ])
)

// Create parameters
let parameters = MessageParameter(
    model: .claude35Sonnet,
    messages: [message],
    maxTokens: maxTokens
)

// Send request
let response = try await service.createMessage(parameters)

SwiftAnthropic v1.8.5

28 Dec 07:15
2bf6a5a
Compare
Choose a tag to compare

What's Changed

New Contributors

Full Changelog: v1.8.4...v1.8.5

SwiftAnthropic v1.8.4

24 Oct 00:18
Compare
Choose a tag to compare

Updating to latest Claude 3.5 Sonnet model claude-3-5-sonnet-latest Default to latest to always have the best model.

Screenshot 2024-10-23 at 5 18 10 PM

Full Changelog: v1.8.3...v1.8.4

SwiftAnthropic v1.8.3

18 Oct 22:51
Compare
Choose a tag to compare

What's Changed

  • Add tool choice options to MessageParameter by @ahammouda in #33

New Contributors

Full Changelog: v1.8.2...v1.8.3

SwiftAnthropic v1.8.2

15 Aug 18:41
a624abc
Compare
Choose a tag to compare

Prompt Caching

Screenshot 2024-08-15 at 11 41 05 AM

Prompt Caching is a powerful feature that optimizes your API usage by allowing resuming from specific prefixes in your prompts. This approach significantly reduces processing time and costs for repetitive tasks or prompts with consistent elements.
For general guidance in Prompt Caching please visit the official Anthropic Documentation.

/// Copied from Anthropic website)

Prompt Caching is in beta

Prompt Caching is now in public beta! To access this feature, you’ll need to include the anthropic-beta: prompt-caching-2024-07-31 header in your API requests.

How to use it with SwiftAnthropic:

You can use it as a System role:

    "system": [
      {
        "type": "text", 
        "text": "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style.\n"
      },
      {
        "type": "text", 
        "text": "<the entire contents of Pride and Prejudice>",
        "cache_control": {"type": "ephemeral"}
      }
    ],

The above is a system role, it translates to this in SwiftAnthropic

let systemPrompt = "You are an AI assistant tasked with analyzing literary works. Your goal is to provide insightful commentary on themes, characters, and writing style"
let someLargePieceOfContentLikeABook: String = "<the entire contents of Pride and Prejudice>"
let systemContent = MessageParameter.Cache(text: systemPrompt, cacheControl: nil)
let cache = MessageParameter.Cache(text: someLargePieceOfContentLikeABook, cacheControl:  .init(type: .ephemeral))
let usersMessage = MessageParameter.Message(role: .user, content: .text("Analyze the major themes in Pride and Prejudice."))
let parameters = MessageParameter(
   model: .claude35Sonnet,
   messages: [usersMessage],
   maxTokens: 1024,
   system: .list([
      systemContent,
      cache
   ]))
let request = try await service.createMessage(parameters)                

Using Prompt Caching in a Message:

let usersPrompt = "Summarize this transcription"
let videoTranscription = "<Some_Long_Text>"
let usersMessageContent = MessageParameter.Message.Content.ContentObject.text(usersPrompt)
let cache = MessageParameter.Message.Content.ContentObject.cache(.init(text: videoTranscription, cacheControl: .init(type: .ephemeral)))
let usersMessage = MessageParameter.Message(role: .user, content: .list([usersMessageContent, cache]))
      let parameters = MessageParameter(
         model: .claude35Sonnet,
         messages: [usersMessage],
         maxTokens: 1024,
         system: .text("You are an AI assistant tasked with analyzing literary works"))

Using Prompt Caching in a Tool:

MessageParameter.Tool(
   name: self.rawValue,
   description: "Get the current weather in a given location",
   inputSchema: .init(
                  type: .object,
                  properties: [
                     "location": .init(type: .string, description: "The city and state, e.g. San Francisco, CA"),
                     "unit": .init(type: .string, description: "The unit of temperature, either celsius or fahrenheit")
                  ],
                  required: ["location"]),
   cacheControl: .init(type: .ephemeral))

SwiftAnthropic v1.8.1

02 Aug 06:42
cea1929
Compare
Choose a tag to compare

AIProxy

What is it?

AIProxy is a backend for iOS apps that proxies requests from your app to Anthropic.
Using a proxy keeps your Anthropic key secret, protecting you from unexpectedly high bills due to key theft.
Requests are only proxied if they pass your defined rate limits and Apple's DeviceCheck verification.
We offer AIProxy support so you can safely distribute apps built with SwiftAnthropic.

How does my SwiftAnthropic code change?

Proxy requests through AIProxy with two changes to your Xcode project:

  1. Instead of initializing service with:

     let apiKey = "your_anthropic_api_key_here"
     let service = AnthropicServiceFactory.service(apiKey: apiKey)
    

Use:

    let service = AnthropicServiceFactory.service(
        aiproxyPartialKey: "your_partial_key_goes_here",
        aiproxyServiceURL: "your_service_url_goes_here"
    )

The aiproxyPartialKey and aiproxyServiceURL values are provided to you on the AIProxy developer dashboard

  1. Add an `AIPROXY_DEVICE_CHECK_BYPASS' env variable to Xcode. This token is provided to you in the AIProxy
    developer dashboard, and is necessary for the iOS simulator to communicate with the AIProxy backend.
    • Go to Product > Scheme > Edit Scheme to open up the "Edit Schemes" menu in Xcode
    • Select Run in the sidebar
    • Select Arguments from the top nav
    • Add to the "Environment Variables" section (not the "Arguments Passed on Launch" section) an env
      variable with name AIPROXY_DEVICE_CHECK_BYPASS and value that we provided you in the AIProxy dashboard

⚠️ The AIPROXY_DEVICE_CHECK_BYPASS is intended for the simulator only. Do not let it leak into
a distribution build of your app (including a TestFlight distribution). If you follow the steps above,
then the constant won't leak because env variables are not packaged into the app bundle.

What is the AIPROXY_DEVICE_CHECK_BYPASS constant?

AIProxy uses Apple's DeviceCheck to ensure
that requests received by the backend originated from your app on a legitimate Apple device.
However, the iOS simulator cannot produce DeviceCheck tokens. Rather than requiring you to
constantly build and run on device during development, AIProxy provides a way to skip the
DeviceCheck integrity check. The token is intended for use by developers only. If an attacker gets
the token, they can make requests to your AIProxy project without including a DeviceCheck token, and
thus remove one level of protection.

What is the aiproxyPartialKey constant?

This constant is safe to include in distributed version of your app. It is one part of an
encrypted representation of your real secret key. The other part resides on AIProxy's backend.
As your app makes requests to AIProxy, the two encrypted parts are paired, decrypted, and used
to fulfill the request to Anthropic.

How to setup my project on AIProxy?

Please see the AIProxy integration guide

⚠️ Disclaimer

Contributors of SwiftAnthropic shall not be liable for any damages or losses caused by third parties.
Contributors of this library provide third party integrations as a convenience. Any use of a third
party's services are assumed at your own risk.

General Changes

  • FIX: make inputTokens optional as it might be null in the event message_delta by @longseespace in #25
  • Add AIProxy support by @lzell in #26

New Contributors

  • @lzell made their first contribution in #26

Full Changelog: v1.8...v1.8.1

Debug print optional.

20 Jul 04:37
Compare
Choose a tag to compare

Now consumers of the library can enable prints on DEBUG builds if desired, prints are now disabled by default.

Example:

let service = AnthropicServiceFactory.service(apiKey: YOUR_API_KEY, debugEnabled: true)