Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Semantic Search Laravel Package

venmail/laravel-semantic-search

Natural language semantic search for Laravel Eloquent models: query in plain English, no embeddings or training. Auto-analyzes your codebase, navigates relationships, supports aggregations, multilingual input, and multi-database setups.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require venmail/laravel-semantic-search
php artisan vendor:publish --tag="semantic-search-config"

Before touching a controller, open config/semantic-search.php and confirm the analysis.scan_directories paths match your app layout. If you keep models in app/Domain/*/Models rather than app/Models, the analyzer will silently find nothing and every query will fall through to the LLM or fail — fix this first.

Then set the minimum viable .env:

SEMANTIC_SEARCH_CACHE_ENABLED=true
SEMANTIC_SEARCH_MAX_ROWS=1000
SEMANTIC_SEARCH_LLM_ENABLED=false

Run a single smoke test in php artisan tinker against a model you know well:

use Venmail\SemanticSearch\Facades\SemanticSearch;

SemanticSearch::search("show users");
SemanticSearch::search("active users older than 25")->limit(5)->get();
SemanticSearch::explainQuery("users with posts created last month");

explainQuery() is your ground truth — it shows how the parser mapped entities, fields, and relationships before SQL ever runs. On a 1-star, 0-dependent package, treat the explanation output as the primary documentation, not the README.

Best first use case: an internal/admin back-office filter bar where a handful of trusted staff type things like "open orders over $500 from last month." Do not ship this as a public-facing search on day one — see the gotchas.

Implementation Patterns

Wrap the facade behind your own gateway. Don't scatter SemanticSearch::search() across controllers. One App\Services\SemanticSearchGateway class keeps the dependency swappable and gives you a single place to enforce allowlists:

final class SemanticSearchGateway
{
    public function search(string $q, array $opts = [])
    {
        return SemanticSearch::search($q, array_merge([
            'locale'  => app()->getLocale(),
            'limit'   => 100,
        ], $opts));
    }
}

Layer authorization on top of the built-in restrictions. The package advertises "authorization," but for day-to-day safety treat restricted_models / restricted_fields as a second line of defense, not the only one. Gate the endpoint with your normal middleware and scope results per-tenant before returning. In the controller:

public function search(Request $request)
{
    $request->validate(['q' => 'required|string|max:200']);

    $results = app(SemanticSearchGateway::class)
        ->search($request->string('q'))
        ->paginate(20);

    return response()->json($results);
}

Golden-query test suite. Because the parser is heuristic and there are no embeddings, phrasing changes behavior. Maintain a fixture of ~20 real user queries per model, assert the resolved model + row set, and run it in CI. This is the single highest-value thing you can do for an unproven package — it catches regressions when you refactor models or upgrade Laravel.

Explain-first development loop. When a query misbehaves, do not tweak prose and re-run blindly. In local/staging only, expose a debug endpoint that returns explainQuery() plus getMetrics() so you can see confidence scores, cache hit rate, and whether the LLM fallback fired.

LLM fallback as an opt-in escalation. Keep SEMANTIC_SEARCH_LLM_ENABLED=false by default. If you enable it, do so only for a specific low-risk endpoint, set a tight timeout (10s default is already generous), and raise confidence_threshold so it fires rarely. Monitor via getMetrics() — a rising fallback rate means your vocabulary/analysis config is under-covering your schema, not that you need more LLM.

Localize per request. Pass app()->getLocale() explicitly; don't rely on the config default. Supported locales: en, es, fr, de, pt.

Cache invalidation as a deploy step. Hook SemanticSearch::clearCache() into your deploy script after migrate. Project analysis, schema, and query results are all cached separately with independent TTLs (default 1h for queries), so a schema change without a flush leaves stale parses.

Aggregations for dashboards, not for lists. "count orders by status" and "sum of transactions by category" are where the package earns its keep in an admin panel; keep CRUD browsing on hand-written queries.

Gotchas and Tips

  • Version churn is real. 0.3.0 replaced SearchEngine and removed QueryParser outright. Any tutorial, Stack Overflow answer, or snippet you find referencing those will fatal. Pin an exact version ("venmail/laravel-semantic-search": "0.3.0") and read CHANGELOG.md before every bump.
  • Stale analysis after refactors. Renaming a model, moving a directory, or adding a column won't be picked up until caches expire or you call SemanticSearch::clearCache(). Make "clear semantic cache" muscle memory after structural changes.
  • Field names must match DB columns exactly. Accessors, casts, $appends, and computed attributes are invisible to the analyzer. "users older than 25" needs a real age or birthdate column — if your model exposes age via accessor only, the query will fail or silently drop the filter.
  • Ambiguous entities are the common failure. Two models with overlapping names (User vs AdminUser vs App\Auth\User) trigger disambiguation prompts or wrong picks. Tune disambiguation.confidence_threshold and prefer distinctive model names in the endpoints you expose.
  • Restricted models/fields via env is fragile. SEMANTIC_SEARCH_RESTRICTED_MODELS is parsed as a comma-joined string; spaces or quoting mistakes produce silently wrong allowlists. Define these as PHP arrays directly in config/semantic-search.php instead.
  • The LLM path ships your schema to OpenAI. Query text, table names, and inferred relationships leave your server. Block it entirely in regulated environments, and if you do enable it, scope the endpoint and review OPENAI_API_KEY handling.
  • max_rows is not a substitute for pagination. Default 10000 is huge; drop to 500–1000 in production and rely on ->paginate().
  • Debugging playbook: (1) explainQuery() — check entity/field resolution; (2) getMetrics() — check confidence and fallback rate, then resetMetrics(); (3) clearCache() and retry; (4) only then consider enabling the LLM. If all four miss, the phrasing is outside the parser's grammar — reword rather than fight it.
  • Zero dependents and one star means no community safety net. Budget for the possibility of forking. Keeping the gateway wrapper and golden-query suite from the patterns section makes that fork a one-file change rather than a refactor.
  • Don't mix 'paginate' and ->paginate() in the same call chain; pick one, or you'll double-apply limits and get surprising page counts.
  • History and suggestions are user-scoped. getHistory() / getSuggestions() depend on an authenticated context — call them from authenticated routes only, and verify they respect your tenancy scoping before exposing them.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager