Download the PHP package murkrow/laravel-rag without Composer

On this page you can find all versions of the php package murkrow/laravel-rag. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-rag

Laravel RAG

Tests Latest Version

A configuration-driven RAG toolkit for Laravel: chunking, embeddings, pgvector retrieval, grounded answering, an MCP server and a Filament control panel.

The package knows nothing about your models. You describe them once in config/rag.php — a model, a relation that yields ordered text, a couple of columns — and everything else follows: ingestion, incremental re-indexing, semantic search with page-accurate citations, a chat endpoint, an MCP server for external agents, and a dashboard to drive it all.

Requirements

PHP 8.2+
Laravel 12 or 13
Database PostgreSQL with the vector extension (pgvector 0.5+)
Embeddings & generation any provider Prism supports — OpenAI, Ollama, VoyageAI, Bedrock, Mistral…
Optional filament/filament ^4 for the panel, laravel/mcp ^1 for the MCP server, laravel/scout for hybrid retrieval

The easiest way to get pgvector is the official image: pgvector/pgvector:pg17. A stock postgres:17 does not ship the extension.

If you already have data in an Alpine-based Postgres, do not simply swap in that image: it is Debian/glibc, and mounting a musl-built PGDATA under a different libc changes collation and can corrupt indexes on text columns. Either dump and restore, or build the extension onto the base you already run:


Installation

rag:install tells you, in plain language, what is missing before anything else can go wrong — a database that cannot host vectors, a missing job_batches table, a corpus with no source configured.

Add your provider key and pick your models:

Then run a worker for the ingestion queue:

Your config/rag.php only needs the keys you actually change: the package's defaults are merged underneath it recursively, so overriding one nested value never drops its siblings. Publish the full, commented file when you want to read the defaults:


Describing your data

A source maps one Eloquent model to a document, and an ordered relation to that document's text segments. It is a class, and it is the only place your own models appear.

Then list it — the only knowledge configuration there is:

Sources are resolved through the container, so a source may take constructor dependencies, and it is a plain object you can instantiate in a test.

position is the number a human would cite — a page, a section, a timestamp in seconds. It ends up on every chunk and in every citation, so pick something meaningful. A model that carries its whole text in one column says SegmentMap::column('body') instead.

Filters

Every filter is one object, applied by the ingestion query, parsed from --filter=name:value, and rendered as the matching field in the Filament form:

Factory Accepts Becomes
Filter::ids('ids', 'id') "1,2,3", [1, 2, 3] whereIn
Filter::range('id_range', 'id') "10-50", "10..50", ['from' =>, 'to' =>] inclusive bounds
Filter::dateRange('published', 'published_at') the same shapes whereDate bounds
Filter::like('title') "garibaldi" like %value%
Filter::in('lang', ['it' => 'Italian']) a list whereIn + multi-select
Filter::boolean('bad_ocr', default: false) truthy / falsy where(col, bool)
Filter::isNull('orphans', 'author') truthy / falsy whereNull / whereNotNull
Filter::callback('recent', fn ($q, $v) => $q->recent($v)) anything your closure

The first argument is the filter's name — what --filter= addresses — and the column defaults to it, which is why two filters can narrow the same column. A blank value means "not filtered"; false is not blank, so default: false constrains every run until someone toggles it. Write your own by implementing SourceFilter.

Per-source chunking is typed too, and only what you set is overridden:

Rows too small to be documents

A table of thousands of short rows -- a gazetteer, a glossary, a term list -- is the wrong shape for one-row-one-document: each vector would carry a handful of tokens and they would all look alike. GroupedEloquentSource groups the rows instead: a grouping expression's distinct values become the documents, and the rows inside a group become its ordered segments, so each chunk holds dozens of related entries.

Positions are ordinals inside the group, so a citation reads "Toponyms - S, entries 120-210". The grouping expression is interpolated into the query: it belongs to the source class and must never come from a request. Filters here select which documents a run covers -- a group that matches is ingested whole.

Not an Eloquent model? Build a source at runtime:


Indexing

--dry-run answers the question worth asking first:

Incremental re-indexing is the default, and it is cheap

Chunks are matched by a hash of their embedding input. Re-running an ingestion over a corpus where one page changed re-embeds the chunks covering that page and keeps every other vector. A nightly rag:ingest books over an unchanged library costs nothing and finishes in seconds.

Three things invalidate a chunk: its text, the document title (it is part of the embedded context header), and the chunking parameters. All three are captured in the hash, so the system can never quietly serve a stale mixture.

Chunking

Text is split into sentence-aligned windows with overlap, which sounds ordinary and is not, because the input is usually worse than prose:

Every parameter is configurable per source, and the chunker is deterministic: the same input always produces the same hashes, which is what makes incremental indexing trustworthy.


Searching and answering

Streaming:

The retrieval pipeline

Over-fetch → optional lexical fusion → score floor → de-duplication → MMR → optional neighbour expansion → top-k.

Two stages earn particular mention. De-duplication is mandatory, not cosmetic: adjacent chunks deliberately share their overlap, so a passage on a boundary reliably matches twice, and without collapsing them half your context window is the same paragraph. MMR trades a little relevance for coverage, because eight paraphrases of one passage are worth barely more than one.

Filters compile to SQL and run inside the ranking query, so a question scoped to one book touches only that book's vectors. For anything the declarative filters cannot express, RetrievalOptions::$constrain takes a closure over the Eloquent builder.

Grounding

The system prompt is a publishable Blade view. The default is deliberately strict: answer only from the numbered context blocks, cite every claim as [#n], refuse rather than speculate, never invent a page number, and quote OCR text as it is rather than silently correcting it.

Two guardrails are enforced in code rather than trusted to the model: when retrieval returns nothing the model is never called at all (an LLM handed no context will answer from its parameters, which is the exact failure a grounded system exists to prevent), and an answer citing nothing is treated as ungrounded and reported as a refusal.

Hybrid retrieval (optional)

Embeddings are weakest at exactly what lexical search is best at: names, dates, catalogue numbers, rare proper nouns. Set RAG_HYBRID_DRIVER=tsvector to fuse a PostgreSQL full-text leg into the ranking with reciprocal rank fusion, or scout to use whichever engine Scout is already configured with.


MCP server

With laravel/mcp installed, the package registers a server automatically — no route file to publish.

search_knowledge semantic search, filterable by source, document and position range
fetch_document read a contiguous span around a hit
answer_question full server-side RAG with citations
documents (resource) what is indexed, so a client can discover identifiers before searching
grounded_answer (prompt) instructions for a client that drives retrieval itself

Rename the tools to suit your domain — the name is most of what a model uses to decide whether to reach for a tool:

Restrict what MCP can reach with rag.mcp.sources. An empty allow-list exposes nothing.


Filament panel

That is the whole installation. Add 'Knowledge' to your panel's navigationGroups(), or point rag.filament.navigation_group at a group you already have.

Styling

Nothing to build. The panel's pages are styled with Filament's own components and inline layout, so they use the stylesheet Filament already publishes -- no custom theme, no Tailwind config, no npm dependency in your application.

That constraint is why you will find inline style attributes and CSS variables (var(--gray-500), var(--primary-500)) rather than utility classes in this package's views: Filament ships a precompiled stylesheet containing its semantic fi-* classes and nothing else, so a utility like grid-cols-4 would not exist unless every host application built a theme for it.


Chat page

A standalone chat UI, served by the package and independent of Filament: its own route, its own stylesheet, its own layout. It exists because the Playground is a diagnostic tool -- single-shot, no memory, every retrieval knob on the form -- and most people asking the corpus a question want an answer and a way to check it, not a retriever to tune.

Nothing to publish and nothing to build. Set RAG_CHAT_PATH to move it, RAG_CHAT_ENABLED=false to switch it off.

Who sees what

Every control maps to an ability named rag.chat.<name>:

Ability Controls Default
view reaching the page at all on
history the sidebar, and saving conversations on
delete renaming, pinning and deleting one's own on
model the model picker and the model label on
sources the knowledge-source picker on
passages the sources panel and the citation pills on
cost per-answer cost, tokens, conversation total on
advanced top_k, min_score, retrieval-only on
feedback thumbs up / down on
export copying a conversation on
all_conversations reading somebody else's off

Each takes one of four shapes in config/rag.php:

Gate::define('rag.chat.cost', ...) in your own provider overrides all of it.

Two things are worth knowing. A closure here cannot be config:cached -- use a [Policy::class, 'method'] array, which is callable and survives var_export(). And the check is not cosmetic: a field whose ability is denied is stripped from the request before validation (Http\Requests\AskRequest::prepareForValidation()), so posting top_k=30 by hand to an account that may not tune retrieval gets the configured default.

Notes


Operating it

Build the index after a bulk load, not before. rag:vector:reindex drops and rebuilds it, which produces a better graph and is substantially faster than incremental inserts. Raise maintenance_work_mem first on a large corpus.

Changing the embedding model invalidates every vector. Vectors from two models are not comparable, and a pgvector column has a fixed width. The change is a deployment, not a setting: update the config, run rag:vector:reindex, then rag:ingest <source> --mode=embeddings_only. rag:status reports how many vectors are stale so the condition is visible rather than silent.

Cost

Roughly, for a 1,000-book library of ~250 pages each at ~350 tokens per page:

Source tokens ~87M
Chunks at 512 tokens, 15% overlap ~200,000
Full index with text-embedding-3-small ~$2
Incremental re-run, nothing changed $0

The dominant cost is wall-clock time against the provider's API, not money. Batches of 96 chunks per request and parallel workers are what move that number; the built-in rate limiter keeps a bulk run from burning its retry budget against a 429.


Extending it

Everything behind a contract can be replaced by binding your own implementation:

Contract Default Why you might swap it
VectorStore PgVectorStore another vector database
EmbeddingProvider Prism an in-house inference service
LanguageModel Prism a bespoke client
Chunker SlidingWindowChunker structure-aware splitting
Retriever / Answerer defaults a different pipeline
LexicalSearch none your own keyword engine
TokenEstimator heuristic TiktokenEstimator for exact counts

For tests, FakeEmbeddingProvider and FakeLanguageModel make the whole pipeline runnable with no API key and no network.


Testing the package

The pgvector suite skips itself when no database is reachable. Point it somewhere with:

CI runs the whole suite, pgvector included, on PHP 8.2–8.4 for every push and pull request.


Contributing

See CHANGELOG.md.

License

MIT — see LICENSE.md.


All versions of laravel-rag with dependencies

PHP Build Version
Package Version
Requires php Version ^8.2
illuminate/bus Version ^12.0|^13.0
illuminate/console Version ^12.0|^13.0
illuminate/contracts Version ^12.0|^13.0
illuminate/database Version ^12.0|^13.0
illuminate/http Version ^12.0|^13.0
illuminate/queue Version ^12.0|^13.0
illuminate/routing Version ^12.0|^13.0
illuminate/session Version ^12.0|^13.0
illuminate/support Version ^12.0|^13.0
illuminate/view Version ^12.0|^13.0
pgvector/pgvector Version ^0.2
prism-php/prism Version ^0.100
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package murkrow/laravel-rag contains the following files

Loading the files please wait ...