AIProxySwift

repository·main·Indexed 19 days ago

https://github.com/aiproxyteam/aiproxyswift

A Swift library for integrating AI provider APIs such as OpenAI, Gemini, and Anthropic into iOS and macOS applications. It provides a unified client interface and optional integration with the AIProxy backend to secure API keys and manage rate limits through features like certificate pinning, DeviceCheck verification, and split key encryption.

Tokens
64.3K
Snippets
135
Records
142
Agent score
16%

What's inside AIProxySwift

  1. Enable OpenAI JSON mode

    main

    To ensure OpenAI returns valid JSON (predecessor to Structured Outputs), set the responseFormat to .jsonObject.

    Important: When using JSON mode, you must also explicitly instruct the model in the system or user prompt to return JSON only.

    import AIProxy
    
    // ... setup openAIService ...
    
    do {
        let requestBody = OpenAIChatCompletionRequestBody(
            model: "gpt-4o",
            messages: [
                .system(content: .text("Return valid JSON only")),
                .user(content: .text("Return alice and bob in a list of names"))
            ],
            responseFormat: .jsonObject
        )
        let response = try await openAIService.chatCompletionRequest(body: requestBody)
        print(response.choices.first?.message.content ?? "")
    } catch {
        // ... handle error ...
    }
  2. Configure AIProxy services (Direct vs. Production)

    main

    AIProxy provides two ways to initialize services depending on your use case:

    1. BYOK (Bring Your Own Key): Use directService methods (e.g., AIProxy.groqDirectService(unprotectedAPIKey:)) to pass your own API keys directly to the provider. This is common for prototyping or specific user-provided key scenarios.
    2. Production: Use standard service methods (e.g., AIProxy.groqService(partialKey:serviceURL:)). This uses a proxy pattern where you provide a partialKey and a serviceURL from your developer dashboard, allowing for more secure and managed production environments.
    /* BYOK */
    let service = AIProxy.groqDirectService(unprotectedAPIKey: "your-groq-key")
    
    /* Production */
    let service = AIProxy.groqService(
        partialKey: "partial-key-from-your-developer-dashboard",
        serviceURL: "service-url-from-your-developer-dashboard"
    )
  3. Use OpenAI Structured Outputs (JSON schemas)

    main

    For strict adherence to a specific data contract, use Structured Outputs by setting responseFormat to .jsonSchema. This requires providing a name, description, and a schema (as a dictionary of AIProxyJSONValue). Setting strict: true ensures the model follows the schema exactly.

    import AIProxy
    
    // ... setup openAIService ...
    
    do {
        let schema: [String: AIProxyJSONValue] = [
            "type": "object",
            "properties": [
                "colors": [
                    "type": "array",
                    "items": [
                        "type": "object",
                        "properties": [
                            "name": ["type": "string", "description": "A descriptive name to give the color"],
                            "hex_code": ["type": "string", "description": "The hex code of the color"]
                        ],
                        "required": ["name", "hex_code"],
                        "additionalProperties": false
                    ]
                ]
            ],
            "required": ["colors"],
            "additionalProperties": false
        ]
    
        let requestBody = OpenAIChatCompletionRequestBody(
            model: "gpt-5.2",
            messages: [
                .system(content: .text("Return valid JSON only, and follow the specified JSON structure")),
                .user(content: .text("Return a peaches and cream color palette"))
            ],
            responseFormat: .jsonSchema(
                name: "palette_creator",
                description: "A list of colors that make up a color pallete",
                schema: schema,
                strict: true
            )
        )
        let response = try await openAIService.chatCompletionRequest(body: requestBody)
        print(response.choices.first?.message.content ?? "")
    } catch {
        // ... handle error ...
    }
  4. Understanding aiproxyswift security constants

    main

    aiproxyswift Security Model

    • aiproxyswift (Partial Key): This constant is intended to be included in your distributed app. It is a partial representation of your OpenAI key (one half of an encrypted version). The other half resides on the AIProxy backend. This allows the backend to pair and decrypt the key to fulfill requests without exposing the full key in your client code.

    • AIPROXY_DEVICE_CHECK_BYPASS: This is a developer-only constant used to skip DeviceCheck integrity checks on the iOS simulator. It should never be included in a distribution build (including TestFlight).

  5. Understanding AIProxy Security and Backend Usage

    main

    AIProxySwift allows you to choose between two request paths:

    1. Direct to Provider: Requests go straight to the AI provider (e.g., OpenAI, Anthropic). This is recommended only for prototyping or 'Bring Your Own Key' (BYOK) use-cases.
    2. Protected through AIProxy Backend: Requests are routed through the AIProxy backend. This is highly recommended if you are using a personal or company API key to keep your keys secure and your billing predictable.

    AIProxy Security Features: When using the AIProxy backend, five levels of security are applied:

    • Certificate pinning
    • DeviceCheck verification
    • Split key encryption
    • Per user rate limits
    • Per IP rate limits
  6. How to use DeviceCheck bypass for simulators

    main

    Since the iOS simulator cannot produce DeviceCheck tokens, you must use the AIPROXY_DEVICE_CHECK_BYPASS environment variable to skip the integrity check during development.

    1. Set the variable in your Test Plan

    1. Open the scheme editor: Product > Scheme > Edit Scheme.
    2. Select Test in the sidebar.
    3. Select your Test Plan.
    4. Under Configurations > Environment Variables, add AIPROXY_DEVICE_CHECK_BYPASS with your value.

    2. Forward the variable in XCTest UI tests

    When running UI tests, you must manually forward the environment variable from the test process to the host simulator:

    func testExample() throws {
        let app = XCUIApplication()
        app.launchEnvironment = [
            "AIPROXY_DEVICE_CHECK_BYPASS": ProcessInfo.processInfo.environment["AIPROXY_DEVICE_CHECK_BYPASS"]!
        ]
        app.launch()
    }

    Warning: Do not include this bypass in production builds. Using environment variables in your test plan ensures the bypass does not leak into your distributed app bundle.

  7. Send PDFs to Anthropic

    main

    To include a PDF in an Anthropic request, use AnthropicDocumentBlockParam.

    1. Load your PDF data from a file URL.
    2. Create an AnthropicDocumentBlockParam using .base64PDF(AnthropicBase64PDFSource(data: ...)).
    3. Add the document block to the content array of an AnthropicMessageParam.
    guard let pdfFileURL = Bundle.main.url(forResource: "mydocument", withExtension: "pdf"),
          let pdfData = try? Data(contentsOf: pdfFileURL) else { return }
    
    let documentBlockParam = AnthropicDocumentBlockParam(
        source: .base64PDF(AnthropicBase64PDFSource(data: pdfData.base64EncodedString()))
    )
    
    let requestBody = AnthropicMessageRequestBody(
        maxTokens: 8192,
        messages: [
            AnthropicMessageParam(
                content: [
                    .textBlock("Provide a very short description of this pdf"),
                    .documentBlock(documentBlockParam),
                ],
                role: .user
            )
        ],
        model: "claude-haiku-4-5-20251001"
    )
  8. Initialize a Gemini service

    main

    You can initialize a Gemini service in two ways depending on your use case:

    1. BYOK (Bring Your Own Key): Use geminiDirectService(unprotectedAPIKey:) for direct access using your own Gemini API key.
    2. Production (AIProxy Managed): Use geminiService(partialKey:serviceURL:) for production environments where you use a partial key and a service URL provided by your AIProxy developer dashboard.
    /* BYOK use case */
    let geminiService = AIProxy.geminiDirectService(
        unprotectedAPIKey: "your-gemini-key"
    )
    
    /* Production use case */
    let geminiService = AIProxy.geminiService(
        partialKey: "partial-key-from-your-developer-dashboard",
        serviceURL: "service-url-from-your-developer-dashboard"
    )
  9. Paginate OpenAI conversation items

    main

    When listing items, you can implement pagination using listItems(conversationID:limit:order:after:secondsToWait:).

    • Use limit to control the number of items returned.
    • Use order (e.g., .asc) to define the sort order.
    • Use the lastID from the current page as the after parameter in the subsequent call to fetch the next page.
    • Check the hasMore property to determine if more items are available.
    // Example of paginating through items
    let page1 = try await openAIService.listItems(
        conversationID: conversation.id,
        limit: 2,
        order: .asc,
        secondsToWait: 120
    )
    
    if page1.hasMore {
        let page2 = try await openAIService.listItems(
            conversationID: conversation.id,
            after: page1.lastID,
            limit: 2,
            order: .asc,
            secondsToWait: 120
        )
    }
  10. Initialize OpenAI service for BYOK or Production

    main

    You can initialize the OpenAI service in two ways depending on your use case:

    1. BYOK (Bring Your Own Key): Use AIProxy.openAIDirectService(unprotectedAPIKey:) when you want to pass a raw OpenAI API key directly.
    2. Production: Use AIProxy.openAIService(partialKey:serviceURL:) to use a partial key and a service URL provided by your AIProxy developer dashboard. This is the recommended approach for production environments.
    import AIProxy
    
    /* For BYOK use cases */
    let openAIService = AIProxy.openAIDirectService(unprotectedAPIKey: "your-openai-key")
    
    /* For production use cases */
    let openAIService = AIProxy.openAIService(
        partialKey: "partial-key-from-your-developer-dashboard",
        serviceURL: "service-url-from-your-developer-dashboard"
    )
  11. How to call your own custom models on Replicate

    main

    To integrate your own custom models hosted on Replicate into AIProxy, follow these steps:

    1. Define Input Schema: Generate an Encodable representation of your model's input. You can find the required format by visiting your model on the Replicate dashboard: Your Model > API > Schema > Input Schema.
    2. Define Output Schema: Generate a Decodable representation of your model's output. Check Your Model > API > Schema > Output Schema on Replicate. If the schema is simple (like a string or array), a typealias may suffice.
    3. Implementation Reference: For inspiration on how to structure these schemas and service calls, refer to ReplicateService+Convenience.swift in the repository.
  12. Use Anthropic prompt caching

    main

    To reduce costs and latency for long prompts, you can use Anthropic's prompt caching. When defining the system parameter in AnthropicMessageRequestBody, use .blocks containing AnthropicSystemTextBlockParam and specify a cacheControl (e.g., AnthropicCacheControlEphemeral(ttl: .oneHour)).

    let requestBody = AnthropicMessageRequestBody(
        maxTokens: 1024,
        messages: [ /* ... */ ],
        model: "claude-haiku-4-5-20251001",
        system: .blocks([
            AnthropicSystemTextBlockParam(
                text: "This is a very long prompt",
                cacheControl: AnthropicCacheControlEphemeral(ttl: .oneHour)
            )
        ])
    )