Native extensions are powerful and heavy. They’re compiled per-platform, installed at the runtime level, and not something a framework can list in composer require and trust to exist. If the PHP AI story stopped at “install my three extensions,” it would remain niche.
So alongside the extensions, I shipped two intentionally thin, pure-PHP Composer packages to carry the architecture.
ai-contracts: interfaces, nothing else
displace/ai-contracts defines five capability interfaces and ships zero implementations:
- Embedder — text in, vector out
- Generator — prompt in, completion out
- VectorIndex — store vectors, search by similarity
- Transcriber — audio in, text out
- Reranker — reorder candidates by relevance to a query
This is the same play PSR-3 ran for logging and PSR-18 ran for HTTP clients: when applications code against an interface, implementations become swappable infrastructure. Your Laravel app type-hints Embedder; whether the container resolves an ext-infer adapter, a cloud API client, or an in-memory fake for your test suite is a wiring decision.
The migration path from cloud-backed search to a fully local stack becomes a service-provider edit.
The five interfaces aren’t arbitrary — they’re the capability set behind essentially every production AI feature: embed, generate, search, transcribe, rerank. Compose them and you get RAG, semantic search, audio archives, structured extraction. The contracts package names the primitives to make it easier for the ecosystem to agree.
ai-toolkit: the code everyone writes anyway
displace/ai-toolkit collects the unglamorous utilities every embedding pipeline inevitably reinvents:
- Chunkers — fixed-size, recursive, and sentence-aware splitting. Chunking quality quietly dominates retrieval quality, and naive substring splitting that bisects sentences poisons embeddings at the source.
- Similarity over packed buffers — cosine and dot product computed directly against the packed float32 format from part 6, no unpack-to-array round trip.
- MRL truncation — Matryoshka-trained models (Qwen3-Embedding included) front-load information so vectors can be truncated to lower dimensions with modest quality loss. Truncating 1024 → 256 quarters your storage; the toolkit handles the cut-and-renormalize correctly.
- pack / unpack helpers — the bridge between PHP arrays and the packed-binary contract.
Pure PHP even without the extensions. The toolkit is useful even if your embeddings come from a cloud API and your vectors live in pgvector.
Allies, not competitors
PHP already has AI framework layers — LLPhant, NeuronAI, Prism — doing good work on orchestration: chains, agents, provider abstractions. A reasonable observer might expect a stack like mine to compete with them. The contracts package is engineered to do the opposite.
Those frameworks orchestrate providers — and today their providers are overwhelmingly HTTP APIs. ext-infer and ext-turbovec, wrapped in ai-contracts adapters, become something new in their catalogs: a local, in-process provider with zero network dependency. The frameworks keep owning developer experience; the extensions own the inference path; the contracts are the boundary where they meet. Their users gain a local option, and the extensions gain distribution through ecosystems that already have mindshare. Interfaces turn would-be competitors into a channel.
Assembling a layer cake
The architecture is bottom-up: Rust engines (llama.cpp, turbovec, whisper.cpp) → native extensions (ext-infer, ext-turbovec, ext-whisper) → pure-PHP contracts and toolkit → frameworks and applications. Each layer depends only downward. Each is also trivially replaceable.
