πŸ“– Overview

Laravel Markdown RAG is a comprehensive Laravel package that transforms Markdown files into an AI-powered knowledge base. It uses vector embeddings, cosine similarity search, and large language models to answer user questions based on your documentation.

πŸ“„

Markdown Indexing

Automatically reads and chunks .md files from a configurable directory with intelligent text splitting.

🧠

Vector Embeddings

Generates semantic embeddings for each text chunk powered by laravel/ai package.

πŸ’¬

AI Chat Interface

Livewire-powered chat where users ask questions and get AI answers grounded in your knowledge base.

πŸ”

Query Rewriting

Optional keyword extraction or AI-powered query rewriting for better search results.

πŸ“Š

Reranking

Optional AI or keyword-based reranking of search results for improved relevance.

πŸ“ˆ

2D Visualization

t-SNE-based embedding visualization via Plotly.js for exploring your knowledge base.

Requirements

Dependency Version
PHP^8.2
Laravel^10 | ^11 | ^12
Livewire^3 | ^4
laravel/ai^0.2.5

πŸš€ Installation

  1. Install via Composer
    bash
    composer require nomanur/laravel-markdown-rag
  2. Run Migrations
    bash
    php artisan migrate

    This creates three tables:

    • histories β€” stores conversation messages
    • knowledge_chunks β€” stores text chunks, embeddings, and source metadata
    • knowledge_documents β€” stores metadata for each indexed markdown file
  3. Publish Configuration (Optional)
    bash
    php artisan vendor:publish --provider="Nomanur\LaravelMarkdownRAGServiceProvider" --tag="config"
  4. Set Environment Variables
    env
    OPENAI_API_KEY=your_api_key_here
    MARKDOWN_CHAT_RATE_LIMIT=100
    MARKDOWN_RERANKING=false
    MARKDOWN_QUERY_REWRITE=false
    AI_PROMPT_REWRITING=false
⚠️

Note: Despite the OPENAI_API_KEY naming, this package uses laravel/ai which supports multiple AI providers including Gemini. Configure your preferred provider in config/ai.php.

Publishable Components

Command What Gets Published
--tag="config" Configuration file to config/laravel-markdown-rag.php
--tag="views" All Blade views for customization
--tag="models" History, KnowledgeChunk, KnowledgeDocument models
--tag="livewire" RagChat, WithoutFlux Livewire components
--tag="migrations" Database migration files

βš™οΈ Configuration

The configuration file is published to config/laravel-markdown-rag.php. Here are all available options:

Key Default Description
openai_api_key env('OPENAI_API_KEY') API key for the AI provider
markdown_chat_rate_limit env('MARKDOWN_CHAT_RATE_LIMIT') Maximum chat history records per user
markdown_reranking false Enable reranking of search results
markdown_query_rewrite false Enable query rewriting before search
ai_prompt_rewriting false Enable AI-powered prompt rewriting
markdown_info_path 'knowledge-base' Path relative to public/ for .md files
markdown_embedding_batch_size 50 Number of embeddings per batch request
markdown_ai_retry_max_attempts 3 Maximum retry attempts for AI requests
markdown_default_agent_prompt (default prompt) Default system prompt for the knowledge agent

πŸ—οΈ System Architecture

The package operates in three distinct phases, creating a complete pipeline from markdown files to AI-powered answers:

Phase 1: Indexing

1
Admin places .md files in public/{markdown_info_path}/ (default: public/knowledge-base/)
2
Admin runs php artisan markdownrag:index
3
TextSplitter reads and chunks each markdown file using paragraph β†’ line β†’ character fallback strategy
4
VectorService generates embeddings for each chunk in batches via laravel/ai
5
Chunks + embeddings stored in knowledge_chunks table; metadata in knowledge_documents
6
Start queue worker: php artisan queue:work to process background jobs

Phase 2: Chat

1
Authenticated user visits /markdownrag
2
Livewire component renders chat interface with existing history
3
User sends message β†’ saved to histories table
4
KnowledgeAgent constructs system prompt + retrieves conversation history
5
Agent calls KnowledgeSearchTool β†’ generates query embedding β†’ cosine similarity search
6
Optional: Query rewriting and reranking refine results
7
AI generates response β†’ saved to histories β†’ displayed to user

Phase 3: Visualization

1
Admin visits /vector-embedding (requires auth + verified)
2
Top 100 chunks fetched from database
3
TSNE reduces high-dimensional embeddings to 2D coordinates
4
Plotly.js renders interactive scatter plot with hover tooltips

Component Architecture

architecture
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  Laravel Markdown RAG                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  .md Files   │──▢│ TextSplitter │──▢│ KnowledgeDoc   β”‚  β”‚
β”‚  β”‚ (public/KB)  β”‚   β”‚  (chunking)  β”‚   β”‚ KnowledgeChunk β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                           β”‚                       β”‚         β”‚
β”‚                           β–Ό                       β–Ό         β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚                    β”‚ VectorService│─▢│   Database       β”‚  β”‚
β”‚                    β”‚ (embeddings) β”‚  β”‚  (chunks/docs)   β”‚  β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                             β”‚               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚               β”‚
β”‚  β”‚  User Chat   │──▢│KnowledgeAgentβ”‚β—€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚  β”‚ (Livewire)   β”‚   β”‚+ SearchTool  β”‚                        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
β”‚         β”‚                   β”‚                                β”‚
β”‚         β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”‚
β”‚         β”‚  β–Ό                β–Ό          β–Ό                    β”‚
β”‚         β”‚ QueryRewrite   RerankSvc   TSNE                   β”‚
β”‚         β”‚ Service                     (visualization)        β”‚
β”‚         β”‚  β–Ό                                                β”‚
β”‚         β”‚ History Model                                     β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“š Indexing System

Artisan Commands

bash
php artisan markdownrag:index          # Index all .md files
php artisan markdownrag:index --clear  # Clear existing chunks first
php artisan queue:work                 # Start queue worker for background jobs

TextSplitter Strategy

The TextSplitter service intelligently chunks markdown content using a multi-strategy approach:

  • Strategy 1: Split by double newlines (\n\n) β€” paragraph-based splitting
  • Strategy 2: Split by single newlines (\n) β€” line-based fallback
  • Strategy 3: Character-based splitting with word boundary awareness
ℹ️

Default Settings: chunkSize: 1000 characters, chunkOverlap: 100 characters. Overlap ensures context from the end of one chunk carries into the next, improving search accuracy.

VectorService Operations

The central service for all vector operations:

php
// Generate embeddings with retry logic
getEmbeddings(array $texts): array

// Store a chunk with its embedding
storeChunk(string $content, array $embedding, string $source, ?string $documentId, array $metadata): void

// Perform cosine similarity search
search(string $query, int $limit, ?string $documentId): Collection

// Reduce dimensions for visualization
reduceDimensions(array $embeddings): array

// Chunk documents and get statistics
chunkDocuments(array $documents): array
countCharacters(array $documents): int
countTokens(array $documents): int

Similarity Search Process

  1. Generates an embedding for the query string
  2. Performs cosine similarity search across all stored chunks
  3. Optionally filters by documentId
  4. Returns results sorted by similarity (highest first)

πŸ’¬ Chat Interface

Routes

Route Middleware Description
GET /markdownrag auth Renders the chat interface (WithoutFlux view)
GET /vector-embedding auth, verified Renders the embedding visualization page

Livewire Components

Two chat components are provided:

RagChat (rag-chat)

  • Limited to 50 messages in display
  • Renders components.rag view
  • Custom-styled chat interface with CSS custom properties
  • Full dark mode support

WithoutFlux (without-flux)

  • No message limit β€” shows all history
  • Renders components.without-flux view
  • Uses Marked.js for markdown rendering of AI responses
  • Uses Tailwind CSS via CDN

KnowledgeSearchTool

The tool available to the AI agent for searching the knowledge base:

  • Name: search_knowledge_base
  • Parameters:
    • query (required, string) β€” the search query
    • document_id (optional, string) β€” limit search to a specific document

Search Pipeline

flow
User Query β†’ [Query Rewrite?] β†’ Embedding β†’ Similarity Search β†’ [Rerank?] β†’ Top 3 Results β†’ AI Response

πŸ“ˆ Embedding Visualization

Access

Visit /vector-embedding while authenticated and verified.

How It Works

  1. Fetches up to 100 KnowledgeChunk records from the database
  2. Extracts their high-dimensional embeddings
  3. Runs t-SNE (t-Distributed Stochastic Neighbor Embedding) to reduce dimensions to 2D
  4. Renders a Plotly.js interactive scatter plot

Features

  • Interactive hover β€” shows source file name and content preview
  • Displays total chunk count and t-SNE algorithm info
  • Dark mode support
  • Empty state with instruction to run markdownrag:index if no chunks exist
ℹ️

TSNE Service: A PHP implementation of t-SNE, ported from Karpathy's tsne.js. Default perplexity: 30.0, learning rate (eta): 100.0. Uses binary search for optimal beta and gradient descent with momentum.

⚑ Services Deep Dive

QueryRewriteService

Improves search quality by rewriting user queries before embedding generation:

Keyword Mode

Enabled when markdown_query_rewrite=true and ai_prompt_rewriting=false. Removes ~150+ English stop words to extract key terms:

Example
"What is the best way to configure the database?" β†’ "best way configure database"

AI Mode

Enabled when ai_prompt_rewriting=true. Uses the LLM to rephrase follow-up questions into standalone queries, incorporating the last 5 conversation messages for context. Includes exponential backoff retry logic.

RerankService

Reorders search results for improved relevance:

Keyword Mode

Enabled when markdown_reranking=true and ai_prompt_rewriting=false. Scores chunks by keyword occurrence frequency, weighted by term length. Sorts by descending score and returns top 3.

AI Mode

Enabled when ai_prompt_rewriting=true. Sends chunks to the AI with instructions to rank them by relevance. Parses the LLM's response for chunk IDs, reorders the collection, and includes a safety fallback for any missed chunks.

πŸ—„οΈ Database Models

History Model

Table: histories

Column Type Description
idbigintPrimary key
user_idbigint (FK)Foreign key to users table
agentstringAgent identifier (e.g., 'knowledge')
rolestring'user' or 'assistant'
contenttextMessage content

Relations: belongsTo(User::class)

KnowledgeChunk Model

Table: knowledge_chunks

Column Type Description
idbigintPrimary key
contenttextText chunk content
embeddingjsonVector embedding array
sourcestringSource filename
document_idstring (indexed)Parent document reference
metadatajson (nullable)Additional metadata

Key Method: similaritySearch($queryEmbedding, $limit, $documentId) β€” performs cosine similarity search

KnowledgeDocument Model

Table: knowledge_documents

Column Type Description
idbigintPrimary key
namestringDocument name (filename)
pathstring (nullable)File path
system_prompttext (nullable)Custom system prompt for this document
tool_descriptiontext (nullable)Custom tool description
metadatajson (nullable)Additional metadata

πŸ€– AI Components

KnowledgeAgent

The core AI agent implementing Agent, Conversational, and HasTools interfaces from laravel/ai:

  • Constructor: Accepts an authenticatedized user, optional documentId, and optional KnowledgeDocument object
  • instructions(): Returns system instructions β€” either from a custom resolver, the document's system_prompt attribute, or the config default
  • messages(): Returns conversation history from the History model, filtered by user and agent='knowledge'
  • tools(): Returns a single KnowledgeSearchTool instance
  • timeout(): Returns 60000ms (60 seconds)

Static Customization Hooks

php
// Override instructions globally
KnowledgeAgent::resolveInstructionsUsing(callable $resolver);

// Override message retrieval globally
KnowledgeAgent::resolveMessagesUsing(callable $resolver);

🎨 Customization

Publishing All Components

bash
# Publish everything at once
php artisan vendor:publish --provider="Nomanur\LaravelMarkdownRAGServiceProvider"

Service Provider Auto-Registration

The service provider automatically registers:

  • Views from resources/views under the laravel-markdown-rag namespace
  • Migrations from src/Database/migrations
  • Routes from routes/web.php
  • Livewire components: rag-chat and without-flux
  • Singleton binding for laravel-markdown-rag
  • Console commands

πŸ”§ Agent & Tool Customization

Customizing Agent Instructions

php
use Nomanur\Ai\Agents\KnowledgeAgent;

// Globally override agent instructions via closure
KnowledgeAgent::resolveInstructionsUsing(function (User $user, ?string $documentId, ?KnowledgeDocument $document) {
    return 'Your custom system instructions here...';
});

Customizing Message Retrieval

php
KnowledgeAgent::resolveMessagesUsing(function (User $user) {
    return History::query()
        ->where('user_id', $user->id)
        ->where('agent', 'custom_agent')
        ->latest()
        ->take(100)
        ->get();
});

Customizing Search Tool Description

php
use Nomanur\Ai\Tools\KnowledgeSearchTool;

KnowledgeSearchTool::resolveDescriptionUsing(function () {
    return 'Your custom tool description...';
});
βœ…

Tip: After publishing models or Livewire components via vendor:publish, you can modify them in app/Models/ or app/Livewire/ to add custom relationships, scopes, or business logic.

πŸ› οΈ Artisan Commands

bash
# Index all .md files from public/knowledge-base/
php artisan markdownrag:index

# Clear existing chunks before indexing
php artisan markdownrag:index --clear

# Start queue worker for background jobs
php artisan queue:work

# Append package routes to your routes/web.php (optional)
php artisan markdownrag:route

markdownrag:index

Indexes all .md files from public/{markdown_info_path}/. Displays a progress bar during indexing and creates KnowledgeDocument records for each file.

markdownrag:route

Appends the package routes to your application's routes/web.php file. Note: Routes are automatically loaded by the service provider by default, so this command is only needed if you've manually removed the route loading.

❓ Troubleshooting

No chunks indexed

  • Ensure .md files exist in public/{markdown_info_path}/
  • Check file permissions on the knowledge-base directory
  • Run with --clear flag if you want to re-index

AI returns empty responses

  • Verify your API key is set in .env
  • Check laravel/ai configuration in config/ai.php
  • Ensure embeddings were generated successfully during indexing

Chat interface not loading

  • Ensure Livewire is installed and properly configured
  • Check that @livewireStyles and @livewireScripts are included in your layout
  • Verify the user is authenticated (routes require auth middleware)

Visualization page is empty

  • Run php artisan markdownrag:index to populate chunks
  • Check that knowledge_chunks table has records with valid embeddings

Queue jobs not processing

  • Ensure the queue worker is running: php artisan queue:work
  • Check your QUEUE_CONNECTION setting in .env (use database, redis, or sync)
  • For development, you can set QUEUE_CONNECTION=sync to process jobs immediately
  • Monitor queue worker logs for errors: php artisan queue:listen
ℹ️

Need more help? Check the GitHub Issues or create a new issue for support.