My AI App is a Laravel-based knowledge assistant that lets each authenticated user build a private searchable knowledge base and ask questions against it.
The system takes user-provided documents, splits them into chunks, generates embeddings for those chunks, retrieves the most relevant chunks for a question, and then sends that retrieved context to an LLM to produce a grounded answer.
The active AI integration in this project uses laravel/ai with Gemini as the configured provider for embeddings and text generation inside the RAG flow.
This project is a basic RAG system with three main layers:
- Knowledge ingestion
- Retrieval
- Answer generation with conversation history
At a high level:
- A user uploads a text file or pastes raw content.
- A user can later replace an existing document and re-index it without creating a duplicate source.
- The system stores the original document as a
knowledge_document. - The content is split into smaller chunks.
- Each chunk gets a high-resolution embedding vector (3072 dimensions).
- The chunks and embeddings are stored in a PostgreSQL vector database (pgvector).
- When the user asks a question (via web or Telegram), the system embeds the question.
- The system performs a vector similarity search using Laravel's native
whereVectorSimilarTomethod, leveraging the database's optimized vector operators for speed and accuracy. - The top matching chunks are used as context for the LLM.
- The generated answer is stored in the conversation along with citations.
This is the top-level source material uploaded by a user.
Stored in knowledge_documents:
user_idtitlesource_namesource_typeoriginal_contentchunk_count
This is the source from which searchable chunks are created.
Each knowledge document is broken into smaller chunks and stored in documents.
Stored in documents:
knowledge_document_idcontentembeddingchunk_indexcharacter_countsource_namemetadata
These chunk rows are the actual retrieval resource used during search.
Each user can have multiple conversations.
Stored in conversations:
user_idtitlelast_message_at
Conversations hold the message history for chat.
Stored in messages:
conversation_idrolecontentcitationsmeta
Messages store both the user question and the assistant answer. Assistant messages can include citations and usage metadata.
The system currently supports two ingestion inputs:
- pasted text content
- uploaded text-based files
- Telegram document attachments (automatically ingested into your knowledge base)
Allowed uploaded file types:
txtmdmarkdowncsvjsonpdf(Fully supported via automated parsing)
The system includes a full-featured Telegram bot that acts as a mobile interface for your private knowledge base.
- Automated Account Linking: Link your site account to Telegram with a single click in your profile using secure deep-linking (
/start <token>). - Mobile Q&A: Ask questions to your knowledge base directly from Telegram.
- Conversational Memory: The bot maintains context across your messages for a natural chat experience.
- Document Ingestion: Forward or upload documents to the bot to automatically add them to your knowledge base.
- Rich UI: Support for Markdown formatting and real-time typing indicators.
- Set
TELEGRAM_API_KEY,TELEGRAM_BOT_URLandTELEGRAM_BOT_USERNAMEin your.env. - Register the webhook using
php artisan telegram:set-webhook <your-url>. - Click "Link Telegram Account" in your user profile.
log
The file contents are read as text, trimmed, and then passed into the ingestion pipeline.
Existing indexed documents can also be replaced through the knowledge base UI. In that flow, the system keeps the same top-level knowledge_document record, deletes its old chunk rows, and regenerates chunks and embeddings from the replacement content.
Ingestion is handled mainly by:
app/Http/Controllers/KnowledgeDocumentController.phpapp/Services/RagService.phpapp/Services/TextChunker.phpapp/Services/OpenAIService.phpapp/Services/TelegramService.phpSmalot\PdfParser\ParserLaravel\Ai\Embeddings
Flow:
- The controller receives either a file or pasted content.
- It validates input and resolves the final title and source info.
RagService::ingest()is called.TextChunker::split()breaks the content into overlapping chunks.OpenAIService::embeddings()callsLaravel\Ai\Embeddingsto generate embeddings through the SDK.- A
knowledge_documentrow is created. - Each chunk is stored in
documentswith its embedding and metadata.
Re-index flow:
- The user clicks
Replaceon an existing indexed document. - The controller validates the replacement file or pasted content.
RagService::reindex()is called.- The existing document's chunk rows are deleted.
- The same
knowledge_documentrow is updated with the new title, source info, original content, and chunk count. - New chunks and embeddings are generated and stored in
documents.
This keeps the document identity stable while refreshing the searchable content cleanly.
Chunking behavior:
- The chunker normalizes large whitespace blocks.
- It targets chunk sizes around 1200 characters.
- It keeps overlap between chunks to preserve context continuity.
Retrieval is handled by:
app/Services/RagService.phpapp/Services/SimilarityService.php
Flow:
- The user sends a question in a conversation.
- The system creates an embedding for that question through
laravel/ai. - The system performs a native pgvector similarity search using the
<=>(cosine distance) operator via Laravel'swhereVectorSimilarTo. - The database filters and sorts the most relevant chunks based on vector distance.
- The top matches are returned directly to the application layer.
- If the best score is too low (below the similarity threshold), the assistant informs the user that the answer is not in the knowledge base.
Unlike traditional RAG implementations that load all embeddings into memory, this system uses database-level vector search. This allows the knowledge base to scale to thousands of documents without compromising response time.
Answer generation is handled by:
app/Http/Controllers/ChatController.phpapp/Services/RagService.phpapp/Services/OpenAIService.phpLaravel\Ai\agent(...)
Flow:
- The question is saved as a user message.
- The system loads up to the most recent 8 conversation messages as history.
- The top retrieved chunks are formatted into a context block.
- That context plus the question and recent history are sent to an anonymous
laravel/aiagent. - The assistant response is saved as a message.
- The assistant message stores citations for the matched chunks used as sources.
- The conversation title is auto-generated from the first user message.
The prompt logic explicitly tells the model:
- answer only from supplied knowledge base context
- say the answer is unavailable if the context is insufficient
The project includes laravel/ai and the live RAG flow now uses it directly.
app/Services/OpenAIService.phpconfig/ai.php
laravel/ai is currently used for two things:
- Embeddings generation
- LLM text generation for grounded answers
For embeddings:
OpenAIService::embeddings()callsLaravel\Ai\Embeddings::for(...)->generate(...)- Provider is forced to
Lab::Gemini - The resulting vectors are stored in the
documents.embeddingcolumn
For answer generation:
OpenAIService::answerQuestion()creates an anonymous SDK agent usingLaravel\Ai\agent(...)- Recent conversation history is converted into
Laravel\Ai\Messages\Messageobjects - The prompt includes the retrieved knowledge context
- The SDK sends the prompt to Gemini and returns structured usage/meta data
- It is not managing the app's own
conversationsormessagestables - It is not using SDK-managed conversation persistence yet
However, it is now deeply integrated into the retrieval layer through the standard Laravel query builder, allowing for seamless vector stores.
Those parts still remain in your own application layer:
RagServicehandles ingestion orchestration and answer orchestrationSimilarityServicehandles cosine similarityConversation,Message,KnowledgeDocument, andDocumentremain your app's own storage model
The current implementation uses Gemini for:
- embeddings
- answer generation
Configured through:
config/ai.phpconfig/services.phpapp/Services/OpenAIService.php
Important note:
- The class is still named
OpenAIService, but it now useslaravel/aiwith Gemini under the hood. - So the active provider for this system's RAG flow is Gemini, routed through the Laravel AI SDK.
Relevant environment variables:
GEMINI_API_KEY=your_gemini_api_key
GEMINI_CHAT_MODEL=gemini-2.5-flash-lite
GEMINI_CHAT_VERSION=v1
GEMINI_EMBEDDING_MODEL=gemini-embedding-001
GEMINI_EMBEDDING_VERSION=v1beta
GEMINI_TIMEOUT=60Related SDK config:
config/ai.phpsets:default => geminidefault_for_embeddings => gemini
config/ai.phpalso maps:providers.gemini.models.text.defaultproviders.gemini.models.embeddings.default
The system is user-scoped.
- Knowledge documents are filtered by
user_id. - Conversations are filtered by
user_id. - Retrieval only searches chunks belonging to the current user's documents.
- One user's indexed resources are not used in another user's retrieval flow.
This is enforced mainly through:
Conversation::scopeForUser()KnowledgeDocument::scopeForUser()abort_unless(... === $request->user()->id, 404)checks in controllerswhereHas('knowledgeDocument', fn (...) => $query->where('user_id', $userId))in retrieval
app/Http/Controllers/DashboardController.phpapp/Http/Controllers/ConversationController.phpapp/Http/Controllers/ChatController.phpapp/Http/Controllers/KnowledgeDocumentController.php
app/Services/RagService.phpapp/Services/TextChunker.phpapp/Services/OpenAIService.phpapp/Services/TelegramService.php
app/Models/KnowledgeDocument.phpapp/Models/Document.phpapp/Models/Conversation.phpapp/Models/Message.php
/dashboard/conversations/knowledge/profile/log-viewer
POST /conversationsGET /conversations/{conversation}DELETE /conversations/{conversation}POST /conversations/{conversation}/messagesGET /knowledge-documentsPOST /knowledge-documentsPUT /knowledge-documents/{knowledgeDocument}/reindexDELETE /knowledge-documents/{knowledgeDocument}
Screenshots and UI references for the project are available here:
- Screenshot folder: https://www.awesomescreenshot.com/s/folder/F02ravZvo8/28d0cabfa1677c60679fc7a9b057a0f0
This folder can be used for:
- landing page screenshots
- authentication screens
- dashboard views
- conversations interface
- knowledge base pages
The project includes opcodesio/log-viewer for inspecting Laravel logs in the browser.
Use:
{APP_URL}/log-viewer
This is useful for checking runtime errors, exceptions, failed requests, and application log entries without opening the raw log files manually.
- PHP 8.2+
- Composer
- Node.js and npm
- Configured database
- Gemini API key
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate
npm install
npm run buildcomposer run devThis starts the Laravel server, queue listener, log tailing, and Vite dev server together.
- Re-indexing replaces a document's existing chunks and embeddings; there is no document version history yet.
- The service name
OpenAIServicedoes not match its current role as a Laravel AI SDK wrapper for Gemini. laravel/aiis not yet managing the application's conversation memory tables directly.- The README reflects the current code implementation, not a generalized future architecture.