Case 4. RAG for internal documentation with LLPhant

A controlled internal documentation QA pipeline in PHP with LLPhant

In this case we move from pure PHP RAG to LLPhant components for embeddings and vector storage while preserving strict control over retrieval, context building, and hallucination prevention.

Example of code:

 
<?php

use LLPhant\Chat\Enums\ChatRole;
use 
LLPhant\Chat\Message;
use 
LLPhant\Chat\OpenAIChat;
use 
LLPhant\Embeddings\Document;
use 
LLPhant\Embeddings\EmbeddingGenerator\OpenAI\OpenAI3SmallEmbeddingGenerator;
use 
LLPhant\Embeddings\VectorStores\Memory\MemoryVectorStore;
use 
LLPhant\OpenAIConfig;

/**
 * @param list<Document> $documents
 * @return list<array{id: int, content: string}>
 */
function documentsForOutputRows(array $documents): array {
    
$rows = [];

    foreach (
$documents as $index => $document) {
        
$rows[] = [
            
'id' => $index + 1,
            
'content' => $document->content,
        ];
    }

    return 
$rows;
}

/**
 * @param list<Document> $relevantDocuments
 */
function buildControlledContextPrompt(array $relevantDocuments, string $query): string {
    
$context = 'You answer strictly based on the context below. ';
    
$context .= "If the answer is missing, say that information is insufficient.\n\n";
    
$context .= "Context:\n";

    foreach (
$relevantDocuments as $document) {
        
$context .= '- ' . ($document->content ?? '') . "\n";
    }

    
$context .= "\nQuestion: {$query}";

    return 
$context;
}

function 
makeDocument(string $content): Document {
    
$document = new Document();
    
$document->content = $content;

    return 
$document;
}

/**
 * @param list<Document> $documents
 * @return array{
 *     query: string,
 *     top_k: int,
 *     documents: list<Document>,
 *     relevant_documents: list<Document>,
 *     context_prompt: string,
 *     answer: string
 * }
 */
function runRagWithLlphant(string $apiKey, array $documents, string $query, int $topK): array {
    
$embeddingConfig = new OpenAIConfig(apiKey: $apiKey, model: 'text-embedding-3-small');
    
$chatConfig = new OpenAIConfig(apiKey: $apiKey, model: 'gpt-4o-mini');

    
$embeddingGenerator = new OpenAI3SmallEmbeddingGenerator($embeddingConfig);
    
$vectorStore = new MemoryVectorStore();

    
$embeddedDocuments = $embeddingGenerator->embedDocuments($documents);
    
$vectorStore->addDocuments($embeddedDocuments);

    
$queryEmbedding = $embeddingGenerator->embedText($query);
    
$relevantDocuments = array_values($vectorStore->similaritySearch($queryEmbedding, $topK));
    
$contextPrompt = buildControlledContextPrompt($relevantDocuments, $query);

    
$chat = new OpenAIChat($chatConfig);
    
$message = new Message();
    
$message->role = ChatRole::User;
    
$message->content = $contextPrompt;

    
$answer = $chat->generateText((string)$message);

    return [
        
'query' => $query,
        
'top_k' => $topK,
        
'documents' => $documents,
        
'relevant_documents' => $relevantDocuments,
        
'context_prompt' => $contextPrompt,
        
'answer' => (string)$answer,
    ];
}