Skip to content

PR 2: Apple Intelligence Provider Implementation #3

Description

@gilmanb1

Overview

Implement the Apple Intelligence provider using the macOS 26 FoundationModels framework. This is the primary/default provider that requires zero external dependencies.

Dependencies

Files to Create/Modify

File Action Description
Sources/SortAI/Core/LLM/AppleIntelligenceProvider.swift Create Main provider implementation
Sources/SortAI/Core/LLM/GenerableTypes.swift Create @generable struct definitions
Tests/SortAITests/AppleIntelligenceTests.swift Create Unit tests

Implementation Details

1. @generable Types for Structured Output

import FoundationModels

@Generable
struct FileCategoryResponse {
    @Guide(description: "The category path like 'Documents / Financial / Reports'")
    var categoryPath: String
    
    @Guide(description: "Confidence from 0.0 to 1.0")
    var confidence: Double
    
    @Guide(description: "Brief explanation for the categorization")
    var rationale: String
    
    @Guide(description: "Relevant keywords extracted from the file")
    var keywords: [String]
}

@Generable
struct EntityExtractionResponse {
    @Guide(description: "List of extracted entities with types")
    var entities: [EntityItem]
}

@Generable
struct EntityItem {
    @Guide(description: "The entity text")
    var text: String
    
    @Guide(description: "Entity type: person, organization, location, date, keyword")
    var type: String
}

2. AppleIntelligenceProvider

import Foundation
import FoundationModels

@available(macOS 26.0, *)
actor AppleIntelligenceProvider: LLMCategorizationProvider {
    let identifier = "apple-intelligence"
    let priority = 1  // Highest priority (default)
    
    private var session: LanguageModelSession?
    private let escalationThreshold: Double
    
    var supportsModelSelection: Bool { false }
    var supportsTemperature: Bool { false }
    var supportsCustomPrompts: Bool { false }
    
    init(escalationThreshold: Double = 0.5) {
        self.escalationThreshold = escalationThreshold
    }
    
    func isAvailable() async -> Bool {
        return LanguageModelSession.isSupported
    }
    
    func categorize(signature: FileSignature) async throws -> CategorizationResult {
        let session = try await getOrCreateSession()
        let prompt = buildCategorizationPrompt(signature: signature)
        
        let response: FileCategoryResponse = try await session.respond(
            to: prompt,
            generating: FileCategoryResponse.self
        )
        
        return CategorizationResult(
            categoryPath: CategoryPath(path: response.categoryPath),
            confidence: response.confidence,
            rationale: response.rationale,
            extractedKeywords: response.keywords,
            provider: identifier,
            escalationThreshold: escalationThreshold
        )
    }
    
    private func getOrCreateSession() async throws -> LanguageModelSession {
        if let session = session { return session }
        let newSession = LanguageModelSession()
        self.session = newSession
        return newSession
    }
    
    private func buildCategorizationPrompt(signature: FileSignature) -> String {
        var prompt = """
        Categorize this file based on its characteristics:
        
        Filename: \(signature.url.lastPathComponent)
        Type: \(signature.kind.rawValue)
        Size: \(signature.size) bytes
        """
        
        if let content = signature.textContent {
            prompt += "\n\nContent preview:\n\(content.prefix(1500))"
        }
        
        if !signature.keywords.isEmpty {
            prompt += "\n\nExtracted keywords: \(signature.keywords.joined(separator: ", "))"
        }
        
        prompt += """
        
        Suggest the most appropriate category path (e.g., "Documents / Work / Reports")
        and explain your reasoning.
        """
        
        return prompt
    }
}

3. Fallback for Pre-macOS 26

/// Stub provider for systems without Apple Intelligence
final class AppleIntelligenceUnavailableProvider: LLMCategorizationProvider, Sendable {
    let identifier = "apple-intelligence"
    let priority = 1
    var supportsModelSelection: Bool { false }
    var supportsTemperature: Bool { false }
    var supportsCustomPrompts: Bool { false }
    
    func isAvailable() async -> Bool { false }
    
    func categorize(signature: FileSignature) async throws -> CategorizationResult {
        throw CategorizationError.providerUnavailable("Apple Intelligence requires macOS 26+")
    }
}

Acceptance Criteria

  • Provider implements LLMCategorizationProvider protocol
  • Uses @Generable for type-safe structured output
  • Gracefully handles macOS version check
  • Session is reused for performance
  • Prompts are well-structured for categorization
  • Confidence values are properly mapped
  • Escalation threshold is configurable
  • All tests pass on macOS 26+
  • Compilation succeeds on macOS 15 (with availability guards)

Testing

@available(macOS 26.0, *)
func testAppleIntelligenceAvailable() async {
    let provider = AppleIntelligenceProvider()
    let available = await provider.isAvailable()
    XCTAssertTrue(available, "Apple Intelligence should be available on macOS 26+")
}

@available(macOS 26.0, *)
func testAppleIntelligenceCategorization() async throws {
    let provider = AppleIntelligenceProvider()
    let signature = FileSignature.mock(
        filename: "quarterly_report_2025.pdf",
        content: "Q4 2025 Financial Summary..."
    )
    
    let result = try await provider.categorize(signature: signature)
    
    XCTAssertEqual(result.provider, "apple-intelligence")
    XCTAssertGreaterThan(result.confidence, 0.0)
    XCTAssertFalse(result.categoryPath.path.isEmpty)
}

Performance Expectations

Based on prototype testing:

  • Simple categorization: 1.5-1.9s
  • Entity extraction: 0.5-0.9s
  • Streaming responses: 1.5s initial

Estimated Size

~300 lines of code

Risk Assessment

Medium - New macOS 26 API. Mitigation: comprehensive @available guards and fallback stub.

Activity

  1. added
    enhancementNew feature or request
    llm-providerLLM provider infrastructure
    phase-2Phase 2 - Parallel development
    on Jan 13, 2026
  2. gilmanb1 commented on Jan 13, 2026

    @gilmanb1
    OwnerAuthor

    Dependencies: Blocked by #2

  3. gilmanb1 commented on Jan 13, 2026

    @gilmanb1
    OwnerAuthor

    ✅ Implementation Complete

    This feature was already implemented as part of the LLM_Research.md implementation. The code exists in the codebase and all 233 tests pass.

    A minor build fix was applied in commit 4341283 to fix the Apple Intelligence availability check API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestllm-providerLLM provider infrastructurephase-2Phase 2 - Parallel development

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions