Search by

eloquage / beacon

zagambila

Local semantic search for PHP: store embeddings, query top-k, and filter by metadata—framework-agnostic.

Package info

github.com/eloquage/beacon

pkg:composer/eloquage/beacon

Fund package maintenance!

Eloquage

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.0 2026-09-15 17:29 UTC

This package is auto-updated.

Last update: 2026-09-15 20:33:01 UTC


README

Small, framework-agnostic semantic search for PHP. Beacon stores embeddings in an isolated in-memory collection and returns exact cosine top-k results with optional scalar metadata filters.

Latest Version on Packagist Tests Total Downloads

Installation

Install via Composer. The pure-PHP implementation works without a framework or native extension:

composer require eloquage/beacon

How to use

use Eloquage\Beacon\Beacon;

$beacon = new Beacon();
echo $beacon->name(); // beacon

$collection = Beacon::collection(3);
$collection->upsert('billing-faq', [1, 0, 0], [
    'section' => 'billing',
    'kind' => 'faq',
]);
$collection->upsert('shipping-faq', [0, 1, 0], [
    'section' => 'shipping',
    'kind' => 'faq',
]);

$hits = $collection->query([1, 0, 0], 5, ['section' => 'billing']);
// [['id' => 'billing-faq', 'score' => 1.0,
//   'metadata' => ['section' => 'billing', 'kind' => 'faq']]]

Beacon::collection($dimension) creates a fresh collection with a positive, fixed dimension. upsert() replaces a record when its ID already exists, so repeated writes do not increase count(). delete($id) returns true when a record was removed and false when the valid ID was absent.

Vectors must be contiguous numeric lists with the collection dimension and a non-zero norm. Metadata and filters are shallow associative maps whose values are strings, integers, floats, booleans, or null; filters use strict equality and compose with AND. Invalid input throws InvalidArgumentException before a record is changed. Query hits contain only id, score, and metadata.

Reference: exact scan and lifecycle

Beacon keeps the collection in process memory. A query validates its input, filters eligible records, computes exact cosine similarity, sorts by descending score, resolves equal scores by ascending bytewise ID, and returns at most k hits. The vector work is O(n·d) for n eligible records of dimension d; smaller k does not make lookup sublinear.

The collection has no persistence or process sharing. Data disappears with the PHP process, and each call to Beacon::collection() is isolated. The v1 API does not accept ANN/HNSW/IVF controls, hosted databases, HTTP configuration, range or OR/NOT filters, or hybrid BM25 search. Lexical search belongs in eloquage/lex.

Explanation: why in-memory first

An in-memory exact scan makes the first implementation predictable: callers can test the complete lifecycle without a service, index build, migration, or operational state. It is a useful fit for small local collections and keeps the public contract focused on records, filtering, and ranking. Persistence and an approximate nearest-neighbor index can be added later behind the same public query shape only when their lifecycle and performance trade-offs are specified.

The pure-PHP implementation is the required behavior and works without a framework or native extension. TypePHP acceleration is optional maintainer work; see TYPEPHP.md for the Docker-only build contract.

Testing

composer test
vendor/bin/pest --coverage --min=90

Project-specific maintainer guidance lives in AGENTS.md, the native build contract in TYPEPHP.md, the project history in CHANGELOG.md, and the license in LICENSE.md.