Skip to main content

Python bindings for Apple's FoundationModels framework - on-device AI (requires macOS 26.0+)

Project description

apple-foundation-models

Unofficial Python bindings for Apple's Foundation Models framework - Direct access to the on-device LLM available in macOS 26+.

Features

  • Async Support: Full async/await support with AsyncSession
  • Tool Calling: Register Python functions as tools for the model to call
  • Structured Outputs: JSON Schema and Pydantic model support
  • Streaming: Real-time token-by-token response generation

Limitations

  • Context Window: 4,096 tokens per session (includes instructions, prompts, and outputs)

Requirements

  • macOS 26.0+ (macOS Sequoia or later)
  • Python 3.9 or higher
  • Apple Intelligence enabled on your device

Installation

From PyPI

pip install apple-foundation-models

Optional dependencies:

# For Pydantic model support in structured outputs
pip install apple-foundation-models[pydantic]

From Source

# Clone the repository
git clone https://github.com/btucker/apple-foundation-models-py.git
cd apple-foundation-models-py

# Install (automatically builds Swift dylib and Cython extension)
pip install -e .

Requirements:

  • macOS 26.0+ (Sequoia) with Apple Intelligence enabled
  • Xcode command line tools (xcode-select --install)
  • Python 3.9 or higher

Note: The Swift dylib is built automatically during installation.

Quick Start

Check Availability

from applefoundationmodels import apple_intelligence_available

if apple_intelligence_available():
    print("Apple Intelligence is ready!")
else:
    print("Apple Intelligence is not available")

Basic Usage

from applefoundationmodels import Session

with Session(instructions="You are a helpful assistant.") as session:
    response = session.generate("What is the capital of France?")
    print(response.text)

Streaming

# Sync streaming
with Session() as session:
    for chunk in session.generate("Tell me a story", stream=True):
        print(chunk.content, end='', flush=True)

Async/Await

import asyncio
from applefoundationmodels import AsyncSession

async def main():
    async with AsyncSession() as session:
        # Use await for non-streaming
        response = await session.generate("What is 2 + 2?")
        print(response.text)

        # Use async for with streaming
        async for chunk in session.generate("Tell a story", stream=True):
            print(chunk.content, end='', flush=True)

asyncio.run(main())

Structured Output

from applefoundationmodels import Session

with Session() as session:
    # Define a JSON schema
    schema = {
        "type": "object",
        "properties": {
            "name": {"type": "string"},
            "age": {"type": "integer"},
            "city": {"type": "string"}
        },
        "required": ["name", "age", "city"]
    }

    # Generate structured response
    response = session.generate(
        "Extract person info: Alice is 28 and lives in Paris",
        schema=schema
    )

    print(response.parsed)  # {'name': 'Alice', 'age': 28, 'city': 'Paris'}

Using Pydantic Models

You can also use Pydantic models for structured outputs (requires pip install pydantic>=2.0):

from applefoundationmodels import Session
from pydantic import BaseModel

class Person(BaseModel):
    name: str
    age: int
    city: str

with Session() as session:
    # Pass Pydantic model directly - no need for JSON schema!
    response = session.generate(
        "Extract person info: Alice is 28 and lives in Paris",
        schema=Person
    )

    print(response.parsed)  # {'name': 'Alice', 'age': 28, 'city': 'Paris'}

    # Parse directly into a Pydantic model for validation
    person = Person(**response.parsed)
    # Or use the convenience method:
    person = response.parse_as(Person)
    print(person.name, person.age, person.city)  # Alice 28 Paris

Tool Calling

Tool calling allows the model to call your Python functions to access real-time data, perform actions, or integrate with external systems. Tools are registered when you create a session and remain available for all generate() calls on that session:

from applefoundationmodels import Session

# Define tools as regular Python functions with docstrings
def get_weather(location: str, units: str = "celsius") -> str:
    """Get current weather for a location."""
    # Your implementation here
    return f"Weather in {location}: 22°{units[0].upper()}, sunny"

def calculate(expression: str) -> float:
    """Evaluate a mathematical expression safely."""
    # Your implementation here
    return eval(expression)  # Use safe_eval in production!

# Register tools at session creation - they'll be available for all generate() calls
with Session(tools=[get_weather, calculate]) as session:
    # The model will automatically call tools when needed
    response = session.generate("What's the weather in Paris and what's 15 times 23?")
    print(response.text)
    # "The weather in Paris is 22°C and sunny. 15 times 23 equals 345."

    # Check if tools were called using the tool_calls property
    if response.tool_calls:
        print(f"Tools called: {len(response.tool_calls)}")
        for tool_call in response.tool_calls:
            print(f"  - {tool_call.function.name}")
            print(f"    ID: {tool_call.id}")
            print(f"    Args: {tool_call.function.arguments}")

    # Check why generation stopped
    print(f"Finish reason: {response.finish_reason}")
    # "tool_calls" if tools were called, "stop" otherwise

See examples/tool_calling_comprehensive.py for complete examples of all supported patterns.

Generation Parameters

from applefoundationmodels import Session

with Session() as session:
    # Control generation with parameters
    response = session.generate(
        "Write a creative story",
        temperature=1.5,      # Higher = more creative (0.0-2.0)
        max_tokens=500        # Limit response length
    )
    print(response.text)

Session Management

from applefoundationmodels import Session

# Create multiple sessions
chat_session = Session(
    instructions="You are a friendly chatbot"
)
code_session = Session(
    instructions="You are a code review assistant"
)

# Each session maintains separate conversation history
chat_response = chat_session.generate("Hello!")
print(chat_response.text)

code_response = code_session.generate("Review this code: ...")
print(code_response.text)

# Clear history while keeping session
chat_session.clear_history()

# Clean up
chat_session.close()
code_session.close()

API Reference

Sync vs Async Pattern

We provide separate session classes for sync and async:

  • Session: Synchronous operations with context manager support
  • AsyncSession: Async operations with async context manager support

Both have identical method signatures - just use await with AsyncSession.

Convenience Functions

def apple_intelligence_available() -> bool:
    """Check if Apple Intelligence is available and ready for use."""
    ...

Session

Manages conversation state and text generation (synchronous).

class Session:
    def __init__(
        instructions: Optional[str] = None,
        tools: Optional[List[Callable]] = None
    ) -> None: ...

    def __enter__() -> Session: ...
    def __exit__(...) -> None: ...

    # Static utility methods
    @staticmethod
    def check_availability() -> Availability: ...
    @staticmethod
    def get_availability_reason() -> str: ...
    @staticmethod
    def is_ready() -> bool: ...
    @staticmethod
    def get_version() -> str: ...

    # Unified generation method
    def generate(
        prompt: str,
        schema: Optional[Union[dict, Type[BaseModel]]] = None,
        stream: bool = False,
        temperature: Optional[float] = None,
        max_tokens: Optional[int] = None
    ) -> Union[GenerationResponse, Iterator[StreamChunk]]: ...

    @property
    def transcript() -> List[dict]: ...
    @property
    def last_generation_transcript() -> List[dict]: ...

    def get_history() -> List[dict]: ...
    def clear_history() -> None: ...
    def close() -> None: ...

AsyncSession

Manages conversation state and text generation (asynchronous).

class AsyncSession:
    def __init__(
        instructions: Optional[str] = None,
        tools: Optional[List[Callable]] = None
    ) -> None: ...

    async def __aenter__() -> AsyncSession: ...
    async def __aexit__(...) -> None: ...

    # Static utility methods (inherited from Session)
    @staticmethod
    def check_availability() -> Availability: ...
    @staticmethod
    def get_availability_reason() -> str: ...
    @staticmethod
    def is_ready() -> bool: ...
    @staticmethod
    def get_version() -> str: ...

    # Unified async generation method
    def generate(
        prompt: str,
        schema: Optional[Union[dict, Type[BaseModel]]] = None,
        stream: bool = False,
        temperature: Optional[float] = None,
        max_tokens: Optional[int] = None
    ) -> Union[Coroutine[GenerationResponse], AsyncIterator[StreamChunk]]: ...

    @property
    def transcript() -> List[dict]: ...
    @property
    def last_generation_transcript() -> List[dict]: ...

    async def get_history() -> List[dict]: ...
    async def clear_history() -> None: ...
    async def close() -> None: ...

Response Types

@dataclass
class GenerationResponse:
    """Response from non-streaming generation."""
    content: Union[str, Dict[str, Any]]
    is_structured: bool

    @property
    def text() -> str: ...  # For text responses
    @property
    def parsed() -> Dict[str, Any]: ...  # For structured responses
    def parse_as(model: Type[BaseModel]) -> BaseModel: ...  # Parse into Pydantic

@dataclass
class StreamChunk:
    """Chunk from streaming generation."""
    content: str  # Text delta
    finish_reason: Optional[str] = None
    index: int = 0

Types

class Availability(IntEnum):
    AVAILABLE = 1
    DEVICE_NOT_ELIGIBLE = -1
    NOT_ENABLED = -2
    MODEL_NOT_READY = -3

class SessionConfig(TypedDict):
    instructions: Optional[str]

class GenerationParams(TypedDict):
    temperature: float
    max_tokens: int

Exceptions

All exceptions inherit from FoundationModelsError:

System Errors:

  • InitializationError - Library initialization failed
  • NotAvailableError - Apple Intelligence not available
  • InvalidParametersError - Invalid parameters
  • MemoryError - Memory allocation failed
  • JSONParseError - JSON parsing error
  • TimeoutError - Operation timeout
  • SessionNotFoundError - Session not found
  • StreamNotFoundError - Stream not found
  • UnknownError - Unknown error

Generation Errors (all inherit from GenerationError):

  • GenerationError - Generic text generation error
  • ContextWindowExceededError - Context window limit exceeded (4096 tokens)
  • AssetsUnavailableError - Required model assets are unavailable
  • DecodingFailureError - Failed to deserialize model output
  • GuardrailViolationError - Content blocked by safety filters
  • RateLimitedError - Session has been rate limited
  • RefusalError - Session refused the request
  • ConcurrentRequestsError - Multiple concurrent requests to same session
  • UnsupportedGuideError - Unsupported generation guide pattern
  • UnsupportedLanguageError - Unsupported language or locale

Tool Errors:

  • ToolNotFoundError - Tool not registered
  • ToolExecutionError - Tool execution failed
  • ToolCallError - Tool call error (validation, schema, etc.)

Examples

See the examples/ directory for complete working examples:

  • basic_chat.py - Simple synchronous conversation
  • basic_async_chat.py - Async conversation with await (no streaming)
  • streaming_chat.py - Async streaming responses
  • structured_output.py - JSON schema validation
  • tool_calling_comprehensive.py - Complete tool calling demonstration with all parameter types

Development

Building from Source

This project uses uv for fast, reliable builds and dependency management:

# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install development dependencies
uv sync --extra dev

# Run tests
uv run pytest

# Type checking
uv run mypy applefoundationmodels

# Format code
uv run black applefoundationmodels examples

# Build wheels
uv build --wheel

You can also use pip if preferred:

pip install -e ".[dev]"
pytest
mypy applefoundationmodels
black applefoundationmodels examples

Project Structure

apple-foundation-models-py/
├── applefoundationmodels/   # Python package
│   ├── __init__.py     # Public API
│   ├── _foundationmodels.pyx  # Cython bindings
│   ├── _foundationmodels.pxd  # C declarations
│   ├── _foundationmodels.pyi  # Type stubs
│   ├── base.py         # Base context manager classes
│   ├── base_session.py # Shared session logic
│   ├── session.py      # Synchronous session
│   ├── async_session.py # Asynchronous session
│   ├── types.py        # Type definitions
│   ├── constants.py    # Constants and defaults
│   ├── tools.py        # Tool calling support
│   ├── pydantic_compat.py # Pydantic compatibility layer
│   ├── exceptions.py   # Exception classes
│   └── swift/          # Swift FoundationModels bindings
│       ├── foundation_models.swift  # Swift implementation
│       └── foundation_models.h      # C FFI header
├── lib/                # Swift dylib and modules (auto-generated)
│   ├── libfoundation_models.dylib    # Compiled Swift library
│   └── foundation_models.swiftmodule # Swift module
├── examples/           # Example scripts
│   └── utils.py        # Shared utilities
└── tests/              # Unit tests

Architecture

apple-foundation-models-py uses a layered architecture for optimal performance:

Python API (session.py, async_session.py)
         ↓
    Cython FFI (_foundationmodels.pyx)
         ↓
    C FFI Layer (foundation_models.h)
         ↓
  Swift Implementation (foundation_models.swift)
         ↓
  FoundationModels Framework (Apple Intelligence)

Performance

  • Cython-compiled for near-C performance
  • Direct Swift → FoundationModels calls (no intermediate libraries)
  • Async streaming with delta-based chunk delivery
  • No GIL during Swift library calls (when possible)

Troubleshooting

Apple Intelligence not available

If you get NotAvailableError:

  1. Ensure you're running macOS 26.0 (Sequoia) or later
  2. Check System Settings → Apple Intelligence → Enable
  3. Wait for models to download (check with Session.get_availability_reason())

Import errors

If you get import errors after installation:

# Rebuild everything (Swift dylib + Cython extension)
pip install --force-reinstall --no-cache-dir -e .

Compilation errors

Ensure you have Xcode command line tools:

xcode-select --install

If the Swift build fails during installation:

  1. Verify macOS version: sw_vers -productVersion (should be 26.0+)
  2. Check Swift compiler: swiftc --version
  3. Clean and reinstall: pip install --force-reinstall --no-cache-dir -e .

License

MIT License - see LICENSE file for details

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

Links

Acknowledgments

This project was inspired by and learned from several excellent works:

  • libai by 6over3 Institute - The original C library wrapper for FoundationModels that demonstrated the possibility of non-Objective-C access to Apple Intelligence. While we ultimately chose a direct Swift integration approach, the libai project's API design and documentation heavily influenced our Python API structure.

  • apple-on-device-ai by Meridius Labs - The Node.js bindings that showed the path to direct FoundationModels integration via Swift. Their architecture of using Swift → C FFI → JavaScript inspired our Swift → C FFI → Cython → Python approach, and their code examples were invaluable for understanding the FoundationModels API.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

apple_foundation_models-0.2.2.tar.gz (159.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

apple_foundation_models-0.2.2-cp314-cp314-macosx_26_0_universal2.whl (123.0 kB view details)

Uploaded CPython 3.14macOS 26.0+ universal2 (ARM64, x86-64)

apple_foundation_models-0.2.2-cp313-cp313-macosx_26_0_universal2.whl (122.4 kB view details)

Uploaded CPython 3.13macOS 26.0+ universal2 (ARM64, x86-64)

apple_foundation_models-0.2.2-cp312-cp312-macosx_26_0_universal2.whl (123.1 kB view details)

Uploaded CPython 3.12macOS 26.0+ universal2 (ARM64, x86-64)

apple_foundation_models-0.2.2-cp311-cp311-macosx_26_0_universal2.whl (122.6 kB view details)

Uploaded CPython 3.11macOS 26.0+ universal2 (ARM64, x86-64)

apple_foundation_models-0.2.2-cp310-cp310-macosx_26_0_universal2.whl (122.5 kB view details)

Uploaded CPython 3.10macOS 26.0+ universal2 (ARM64, x86-64)

apple_foundation_models-0.2.2-cp39-cp39-macosx_26_0_universal2.whl (122.6 kB view details)

Uploaded CPython 3.9macOS 26.0+ universal2 (ARM64, x86-64)

File details

Details for the file apple_foundation_models-0.2.2.tar.gz.

File metadata

  • Download URL: apple_foundation_models-0.2.2.tar.gz
  • Upload date:
  • Size: 159.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for apple_foundation_models-0.2.2.tar.gz
Algorithm Hash digest
SHA256 5a9d1739b3a75afa382f908a92a86a9f9c125662631e103c6d420aac26da4267
MD5 b42c151a049026b8a56ce66c283f14d9
BLAKE2b-256 f6892f02dd7eb049d1fafe3a013d2ce7fcad0c513dac444797ba9b4a647f4917

See more details on using hashes here.

Provenance

The following attestation bundles were made for apple_foundation_models-0.2.2.tar.gz:

Publisher: publish-to-pypi.yml on btucker/apple-foundation-models-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file apple_foundation_models-0.2.2-cp314-cp314-macosx_26_0_universal2.whl.

File metadata

File hashes

Hashes for apple_foundation_models-0.2.2-cp314-cp314-macosx_26_0_universal2.whl
Algorithm Hash digest
SHA256 d41c1714fa4acb7e08f368adef062e618f60871d7c6c7fd3b1ec5ac2683b6fe2
MD5 0432b7cf70c1c77863b2c8bc81df7709
BLAKE2b-256 851a845ebe5ef71b1b3a97f17a5f6cf72ae182bc8ab7df4577549bd7157fa8e3

See more details on using hashes here.

Provenance

The following attestation bundles were made for apple_foundation_models-0.2.2-cp314-cp314-macosx_26_0_universal2.whl:

Publisher: publish-to-pypi.yml on btucker/apple-foundation-models-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file apple_foundation_models-0.2.2-cp313-cp313-macosx_26_0_universal2.whl.

File metadata

File hashes

Hashes for apple_foundation_models-0.2.2-cp313-cp313-macosx_26_0_universal2.whl
Algorithm Hash digest
SHA256 6e8ff2390b77103c939d0c030df4672d003dc26c5cda1e55a56e8c57e7e01944
MD5 6369ecdd64bf9b1568831cbcbc963129
BLAKE2b-256 45c70c0a5a042cbad716c9e2159c5d362a83c85fdf4db3daa52a8a3110249f72

See more details on using hashes here.

Provenance

The following attestation bundles were made for apple_foundation_models-0.2.2-cp313-cp313-macosx_26_0_universal2.whl:

Publisher: publish-to-pypi.yml on btucker/apple-foundation-models-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file apple_foundation_models-0.2.2-cp312-cp312-macosx_26_0_universal2.whl.

File metadata

File hashes

Hashes for apple_foundation_models-0.2.2-cp312-cp312-macosx_26_0_universal2.whl
Algorithm Hash digest
SHA256 50cd51cfc1bc85d17c1c77b43f720fb32e0e6c97c3051a7c4794bc70968cad8b
MD5 9ebafb3c1b729c49f23b49ca73784ba9
BLAKE2b-256 e829d736faebe6057164517b3319353fa5c833fef0778138c806f662a73a2692

See more details on using hashes here.

Provenance

The following attestation bundles were made for apple_foundation_models-0.2.2-cp312-cp312-macosx_26_0_universal2.whl:

Publisher: publish-to-pypi.yml on btucker/apple-foundation-models-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file apple_foundation_models-0.2.2-cp311-cp311-macosx_26_0_universal2.whl.

File metadata

File hashes

Hashes for apple_foundation_models-0.2.2-cp311-cp311-macosx_26_0_universal2.whl
Algorithm Hash digest
SHA256 74b23669e17ea2ce40cb669d4b778a539ae9b09964d483b8da71318321865db6
MD5 a0e9663617141b5ad6c3c316563a2087
BLAKE2b-256 f0f560120a75ea2f254b8586af7217111d206973c595fd8a3a2a07864c4c65ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for apple_foundation_models-0.2.2-cp311-cp311-macosx_26_0_universal2.whl:

Publisher: publish-to-pypi.yml on btucker/apple-foundation-models-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file apple_foundation_models-0.2.2-cp310-cp310-macosx_26_0_universal2.whl.

File metadata

File hashes

Hashes for apple_foundation_models-0.2.2-cp310-cp310-macosx_26_0_universal2.whl
Algorithm Hash digest
SHA256 c1bd01f3abc4253efafc22d4c9340c93263528318e6eda49141fcd4842a2a320
MD5 83a664d2843250ea0d782bbe484bd3f3
BLAKE2b-256 9cae194691f149c9a4f3a6ac214405231de57ca4c087478a54b5fcc289f7d278

See more details on using hashes here.

Provenance

The following attestation bundles were made for apple_foundation_models-0.2.2-cp310-cp310-macosx_26_0_universal2.whl:

Publisher: publish-to-pypi.yml on btucker/apple-foundation-models-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file apple_foundation_models-0.2.2-cp39-cp39-macosx_26_0_universal2.whl.

File metadata

File hashes

Hashes for apple_foundation_models-0.2.2-cp39-cp39-macosx_26_0_universal2.whl
Algorithm Hash digest
SHA256 448169d6142930bbb64e48a63346a219cd3795d4d799629c6686a99e22f53b95
MD5 c7e2ac9e8b73cf5a4068284a6a54c84c
BLAKE2b-256 fe9f5a14788aa02931fc8ef7978bf93767f6a391f32875578605834782813879

See more details on using hashes here.

Provenance

The following attestation bundles were made for apple_foundation_models-0.2.2-cp39-cp39-macosx_26_0_universal2.whl:

Publisher: publish-to-pypi.yml on btucker/apple-foundation-models-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page