Releases: jamesrochabrun/SwiftAnthropic
SwiftAnthropic v2.1.1

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...
SwiftAnthropic v2.1.0
Adding Citations support in the Swift Library
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
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:
- adding optional
is_error
field to tool result by @elliottburris in #36 - Count Message tokens support by @jamesrochabrun in #38
New Contributors
- @elliottburris made their first contribution in #36
Full Changelog: v1.8.6...v2.0.0
SwiftAnthropic v1.8.6
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
What's Changed
- Make
MessageResponse.Content.ToolUse
Codable
by @Liampronan in #34 - Add claude-3-5-haiku-latest by @puemos in #35
New Contributors
- @Liampronan made their first contribution in #34
- @puemos made their first contribution in #35
Full Changelog: v1.8.4...v1.8.5
SwiftAnthropic v1.8.4
Updating to latest Claude 3.5 Sonnet model claude-3-5-sonnet-latest
Default to latest to always have the best model.

Full Changelog: v1.8.3...v1.8.4
SwiftAnthropic v1.8.3
What's Changed
- Add tool choice options to MessageParameter by @ahammouda in #33
New Contributors
- @ahammouda made their first contribution in #33
Full Changelog: v1.8.2...v1.8.3
SwiftAnthropic v1.8.2
Prompt Caching
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
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:
-
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
- 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 nameAIPROXY_DEVICE_CHECK_BYPASS
and value that we provided you in the AIProxy dashboard
- Go to
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 eventmessage_delta
by @longseespace in #25 - Add AIProxy support by @lzell in #26
New Contributors
Full Changelog: v1.8...v1.8.1
Debug print optional.
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)