SwiftAnthropic

repository·main·Indexed 42 days ago

https://github.com/jamesrochabrun/swiftanthropic

An open-source Swift package providing a streamlined interface for Anthropic's public API endpoints. It supports Text Completion, the Messages API, Vision, and advanced features like tool use (function calling), prompt caching, and hosted tools such as the Text Editor and Web Search. Compatible with iOS 15+, macOS 12+, and server-side Swift on Linux.

Tokens
12.9K
Snippets
40
Records
45
Agent score
4%

What's inside SwiftAnthropic

  1. Platform Support for SwiftAnthropic

    main

    SwiftAnthropic is designed to work across multiple environments:

    • Apple Platforms: Supports iOS 15+ and macOS 12+.
    • Linux: Supports server-side Swift. On Linux, the package uses AsyncHTTPClient to avoid URLSession bugs in the Foundation framework.

    Note: AIProxy support is exclusive to Apple platforms.

  2. Construct JSON schemas for tool inputs

    main

    The JSONSchema and Property structs allow you to define the expected input format for tools using a subset of JSON Schema.

    Key components include:

    • JSONType: The type of the value (.string, .integer, .boolean, .array, .object, .number, or .null).
    • properties: A dictionary of Property objects for objects.
    • required: An array of property names that must be present.
    • items: For array types, defines the schema of the elements.
    • Constraints: Supports minimum, maximum, pattern, enum (via enumValues), and more.
    let schema = JSONSchema(
        type: .object,
        properties: [
            "age": Property(type: .integer, minimum: 18, maximum: 120)
        ],
        required: ["age"]
    )
  3. How Skills and Containers work in Messages

    main

    Skills are tools that extend Claude's capabilities (e.g., spreadsheet manipulation via xlsx). To use a skill in a message, you must:

    1. Include code execution tools in the tools parameter.
    2. Reference the skill within a container object.

    Containers allow you to maintain state across multiple turns. When you make a request using a skill, the response may include a container.id. For better performance in multi-turn conversations, pass this id back in the container parameter of your subsequent requests. This allows Claude to reuse the same execution environment.

    // Example of a message using a skill and code execution tools
    let parameters = MessageParameter(
        model: .claude37Sonnet,
        messages: [userMessage],
        maxTokens: 4096,
        tools: [.hosted(type: "code_execution_20250825", name: "code_execution")],
        container: .init(
            id: nil, // Use nil for the first request; reuse an existing ID for subsequent turns
            skills: [
                .init(type: .anthropic, skillId: "xlsx", version: "latest")
            ]
        )
    )
  4. Maintain reasoning continuity in multi-turn conversations

    main

    To maintain reasoning continuity in multi-turn conversations, you must include the thinking blocks (and any redacted thinking blocks) as part of the assistant's message content in the conversation history.

    When building the messages array for the next turn, the assistant's message should use a .list content type containing both the .thinking (with its signature) and .text content objects.

    // Create all content objects for the assistant message
    var contentObjects = thinkingBlocks // contains .thinking and .redactedThinking
    contentObjects.append(.text(responseText))
    
    // Save assistant's response to conversation history with thinking blocks included
    messages.append(MessageParameter.Message(
        role: .assistant,
        content: .list(contentObjects)
    ))
  5. Enable the Skills API via Beta Headers

    main

    The Skills API is currently in beta. To use skills (like xlsx or text_editor), you must initialize your AnthropicService with the appropriate beta headers using AnthropicServiceFactory.service(apiKey:betaHeaders:).

    Required beta headers include:

    • skills-2025-10-02
    • code-execution-2025-08-25
    let service = AnthropicServiceFactory.service(
        apiKey: apiKey,
        betaHeaders: ["skills-2025-10-02", "code-execution-2025-08-25"]
    )
  6. Use Prompt Caching with System Prompts, Messages, Tools, and Documents

    main

    Prompt Caching (in public beta) allows you to reduce costs and latency by caching large context blocks. To use it, ensure your request includes the prompt-caching-2024-07-31 beta header. In SwiftAnthropic, you apply caching using .init(type: .ephemeral) on various components:

    • System Prompts: Use MessageParameter.Cache within the system parameter.
    • Messages: Use MessageParameter.Message.Content.ContentObject.cache within a message's content list.
    • Tools: Use cacheControl on a MessageParameter.Tool definition.
    • Documents: Use cacheControl on the MessageParameter.Message.Content.DocumentSource.pdf object.
    • Tool Results: Use cacheControl on the toolResult content object.
    // Example: Caching a large document
    let pdfData = // your PDF data
    let base64PDF = pdfData.base64EncodedString()
    
    let documentSource = try MessageParameter.Message.Content.DocumentSource.pdf(
        base64Data: base64PDF,
        title: "Large Research Document",
        cacheControl: .init(type: .ephemeral)
    )
    
    let message = MessageParameter.Message(
        role: .user,
        content: .list([
            .document(documentSource),
            .text("Please analyze this document")
        ])
    )
    
    let parameters = MessageParameter(
        model: .claude35Sonnet,
        messages: [message],
        maxTokens: 1024
    )
    
    let response = try await service.createMessage(parameters)
  7. Implement Function Calling (Tool Use)

    main

    Claude can interact with external tools by providing a tools array in the MessageParameter.

    When Claude decides to use a tool, the MessageResponse will have a stop_reason of tool_use and a content block of type tool_use. This block includes:

    • id: A unique identifier to match tool results later.
    • name: The name of the tool being called.
    • input: An object containing the arguments for the tool.

    Disabling Parallel Tool Use

    By default, Claude may use multiple tools. To restrict Claude to at most one tool (when type is .auto) or exactly one tool (when type is .any or .tool), set disableParallelToolUse: true in the ToolChoice object.

    let maxTokens = 1024
    let weatherTool = MessageParameter.Tool.function(
        name: "get_weather", 
        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"]
        )
    )
    
    let messageParameter = MessageParameter.Message(role: .user, content: "What is the weather like in San Francisco?")
    let parameters = MessageParameter(model: .claude3Opus, messages: [messageParameter], maxTokens: maxTokens, tools: [weatherTool])
    
    let message = try await service.createMessage(parameters)
    
    // To disable parallel tool use:
    let toolChoice = ToolChoice(type: .auto, disableParallelToolUse: true)
    let parametersWithLimitedTools = MessageParameter(
        model: model,
        messages: messages,
        maxTokens: maxTokens,
        toolChoice: toolChoice
    )
  8. Use Extended Thinking with Claude 3.7 Sonnet

    main

    Claude 3.7 Sonnet supports an 'extended thinking' mode that allows the model to perform step-by-step reasoning before providing a final answer. To use this, you must provide a Thinking object within your MessageParameter specifying a budgetTokens value.

    Key Constraints:

    • Minimum Budget: 1,024 tokens.
    • Compatibility: Thinking mode is not compatible with temperature, top_p, top_k, or forced tool use.
    • Billing: All thinking tokens (including redacted ones) are billed as output tokens.
    • Context: Thinking blocks from previous turns do not count against your context window.
    // Enable thinking with a budget of 16,000 tokens
    let parameters = MessageParameter(
        model: .claude37Sonnet,
        messages: [userMessage],
        maxTokens: 4000,
        thinking: .init(budgetTokens: 16000)
    )
    
    let response = try await service.createMessage(parameters)
  9. Configure AIProxy for the iOS Simulator

    main

    Because the iOS simulator cannot produce Apple's DeviceCheck tokens, AIProxy requires a bypass token to allow communication during development.

    Steps to configure in Xcode:

    1. Go to Product > Scheme > Edit Scheme.
    2. Select Run in the sidebar.
    3. Select Arguments from the top navigation.
    4. In the Environment Variables section (not 'Arguments Passed on Launch'), add a new variable:
      • Name: AIPROXY_DEVICE_CHECK_BYPASS
      • Value: [The token provided in your AIProxy developer dashboard]

    ⚠️ Warning: This bypass token is for the simulator only. Do not include it in distribution builds. Using the Environment Variables section in Xcode ensures the token is not packaged into the app bundle.

  10. Initialize the AnthropicService

    main

    To use SwiftAnthropic, import the package and initialize the service using AnthropicServiceFactory. You can provide an API key, override the API version, specify a custom base path, or include beta headers for experimental features.

    import SwiftAnthropic
    
    // Basic initialization
    let apiKey = "YOUR_ANTHROPIC_API_KEY"
    let service = AnthropicServiceFactory.service(apiKey: apiKey)
    
    // Initialization with API version and custom base path
    let apiVersion = "2023-06-01"
    let basePath = "https://myservice.com"
    let service = AnthropicServiceFactory.service(apiKey: apiKey, apiVersion: apiVersion, basePath: basePath)
    
    // Initialization with Beta headers (required for Beta features)
    let betaHeaders = ["prompt-caching-2024-07-31", "max-tokens-3-5-sonnet-2024-07-15"]
    let service = AnthropicServiceFactory.service(apiKey: apiKey, betaHeaders: betaHeaders)
  11. Install SwiftAnthropic via Swift Package Manager

    main

    To add SwiftAnthropic to your Xcode project, follow these steps:

    1. Open your Swift project in Xcode.
    2. Navigate to File -> Add Package Dependency.
    3. Enter the following URL in the search bar: https://github.com/jamesrochabrun/SwiftAnthropic
    4. Select the version you wish to install.
    5. Click Add Package to complete the installation.
    // No specific command provided, follow Xcode UI steps using:
    // https://github.com/jamesrochabrun/SwiftAnthropic
  12. Use the Anthropic Text Editor Tool

    main

    The Text Editor Tool allows Claude to view and modify files directly. This is a hosted tool and is only compatible with specific models:

    • Claude 3.7 Sonnet: Use text_editor_20250124
    • Claude 3.5 Sonnet: Use text_editor_20241022

    Available Commands

    When processing the tool_use content block, you must implement the following commands from the input object:

    1. view: Examine file contents. Requires path and optional view_range.
    2. str_replace: Replace text. Requires path, old_str, and new_str.
    3. create: Create a new file. Requires path and file_text.
    4. insert: Insert text at a location. Requires path, insert_line, and new_str.
    5. undo_edit: Revert the last edit. Requires path.
    // Define the text editor tool
    let textEditorTool = MessageParameter.Tool.hosted(
        type: "text_editor_20250124", // Match version to model
        name: "str_replace_editor"
    )
    
    let messageParameter = MessageParameter.Message(role: .user, content: .text("There's a syntax error in my primes.py file. Can you help fix it?"))
    
    let parameters = MessageParameter(
        model: .claude37Sonnet, 
        messages: [messageParameter], 
        maxTokens: 1024,
        tools: [textEditorTool]
    )
    
    let message = try await service.createMessage(parameters)
    
    // Handle the tool use response
    for content in message.content {
        if case .toolUse(let id, let name, let input) = content {
            if let command = input["command"]?.stringValue {
                switch command {
                case "view":
                    // Implement file reading logic
                case "str_replace":
                    // Implement text replacement logic
                case "create":
                    // Implement file creation logic
                case "insert":
                    // Implement text insertion logic
                case "undo_edit":
                    // Implement undo logic
                default:
                    break
                }
            }
        }
    }