RAG (Retrieval-Augmented Generation или генерация, дополненная поиском) - это метод искусственного интеллекта, сочетающий генеративную большую языковую модель (LLM) с внешней базой знаний для создания более точных, контекстно-зависимых и актуальных ответов. Принцип его работы заключается в том, что сначала извлекается релевантная информация из набора документов или источников данных, а затем эта информация передается в LLM для формирования окончательного ответа. Этот процесс позволяет модели выдавать более точные ответы, менее подверженные “галлюцинациям”, и ее можно обновлять без дорогостоящего переобучения.

Сегодня мы разберёмся, как собрать базовую RAG-систему на PHP (да, да, не надо удивляться) с помощью фреймворка Neuron AI. Это будет наш маленький proof-of-concept - минимально работающий, но вполне реальный пример.

Ну что, начнём генерацию?

1. Что вообще такое RAG и зачем оно нужно

Итак, если коротко: RAG - это подход, при котором нейросеть не просто “фантазирует” ответ, а сначала ищет релевантные данные в базе (например, Wiki, документации, БД), а потом использует их при генерации ответа.

В классике жанра это два шага:

  1. Retrieval - поиск нужных фрагментов (через векторный поиск).

  2. Generation - генерация ответа с использованием найденных данных.

Типы RAG бывают разные - от простого “векторного поиска + LLM” до сложных систем с re-ranking, кэшированием контекста и цепочками размышлений (chain-of-thought, но об этом лучше не на проде). Мы не будем сильно углубляться в теорию, в Интернете и на Хабре есть огромное количество адекватных материалов, например: тут или тут, в том числе как и когда возник этот подход и Генерация дополненная поиском (спойлер - начал формироваться примерно в 2020 году)

Схема работы RAG-системы
Схема работы RAG-системы

Где это можно применять:

  • корпоративные чаты для поиска по документации;

  • голосовые ассистенты;

  • внутренние help-боты для службы поддержки;

  • ну и просто чтобы похвастаться коллегам.

2. Почему PHP и Neuron AI?

Хороший вопрос.

Можно, конечно, собрать RAG на Python - взять LangChain, LlamaIndex, Milvus, Chroma и почувствовать себя настоящим Data Scientist'ом. Примеров и туториалов полно. Но если у вас весь веб-проект уже на PHP, зачем тянуть Python только ради векторного поиска? Честно говоря, я не вижу в этом смысла - да и не хочу поднимать ещё один стек, если всё можно сделать "по-домашнему", на PHP.

Поэтому, тут на сцену выходит Neuron AI - лёгкий PHP-фреймворк, который добавляет в мир PHP то, что раньше казалось невозможным: работу с LLM, embeddings и даже собственный VectorStore. Раньше я уже писал про этот фреймворк и одна из причин написания этой статьи - обещание показать реальные примеры его применения.

Возможно, он не идеален, но зато прост, понятен и интегрируется в существующие PHP-приложения за пару строк. В духе старого доброго Laravel - но в данном случае для AI.

3. Что мы будем делать

Создадим AI-агента, который сможет отвечать на вопросы, используя вашу внутреннюю базу знаний.

Технически мы сделаем простую RAG-систему, которая:

  • создаёт векторное хранилище из документов;

  • ищет нужные куски по запросу (выбирает topK ближайших документов по векторному сходству);

  • генерирует ответ на основе найденного.

Представим, что у нас есть внутренняя база вроде Wiki или Confluence, и мы хотим, чтобы бот мог отвечать на вопросы по ней.

RAG для этого подходит идеально, особенно когда в команду приходит новичок, для которого ваша документация - это настоящая "Terra Incognita".

4. Процесс с примерами кода

4.1. Требования

  • PHP 8.2+

  • Composer

  • Neuron AI (composer require neuron-ai/neuron)

  • Ключ для LLM API (например, OpenAI)

4.2. Почему FileVectorStore

Для примера мы не будем поднимать никаких векторных баз данных типа Faiss или Pinecone. Нам достаточно обычного FileVectorStore - простой файловой базы, где всё хранится в файле .store.

Да, не масштабируется, но зато не ломается. Для демо - то что нужно. Впрочем, если у вас всего лишь пара тысяч документов, то вполне сойдёт и для локального использования в реальном проекте.

4.3. Установка и инициализация

Добавим необходимые пакеты

composer require neuron-ai/neuron
composer require openai-php/laravel

Создаём структуру проекта:

/demo/
  ├── store/
  │    ├── docs/
  ├── src/
  │    ├── Commands/
  │    ├── Classes/
  └── index.php

4.4. Создание документной базы

В качестве примера поместим в нашу store/docs/ четыре демонстрационных документа в формате .md (он используется по умолчанию, но вы можете работать и другими форматами, такими как HTML, XML, PDF и т.д.). Эти четыре Markdown-документа, содержат описание компании, культуру, услуги и техническую экспертизу.

company-culture.md
# Linx Team - Company Culture & Values

## Core Values

### Innovation
We embrace new technologies and methodologies. Our team is constantly learning and experimenting with cutting-edge solutions to stay ahead of industry trends.

### Quality
We are committed to delivering high-quality code and products. Every line of code is reviewed, tested, and optimized for performance and maintainability.

### Collaboration
We believe in the power of teamwork. Our collaborative culture encourages open communication, knowledge sharing, and mutual support.

### Continuous Learning
Professional growth is important to us. We invest in our team's development through training, conferences, and mentorship programs.

### Transparency
We maintain open and honest communication with our clients and team members. Clear expectations and regular updates ensure successful partnerships.

## Work Environment

### Remote-Friendly
We offer flexible work arrangements with options for remote work, allowing our team to maintain work-life balance.

### Modern Office
Our N city office is equipped with modern amenities and collaborative spaces for team members who prefer working on-site.

### Team Events
Regular team building activities, workshops, and social events foster strong relationships and company culture.

## Professional Development

### Training Programs
We provide access to online courses, certifications, and training programs to help team members advance their skills.

### Mentorship
Experienced team members mentor junior developers, fostering knowledge transfer and career growth.

### Conference Attendance
We support team members attending industry conferences and speaking at events.

### Open Source Contributions
We encourage and support contributions to open source projects, giving back to the community.

## Benefits

### Competitive Compensation
We offer competitive salaries and benefits packages.

### Health Insurance
Comprehensive health insurance coverage for employees and their families.

### Flexible Hours
Flexible working hours that accommodate personal needs while meeting project deadlines.

### Professional Development Budget
Annual budget for training, courses, and professional development.
company-overview.md
# Linx Team - Company Overview

## About Us
Linx Team is a leading software development company specializing in web and mobile applications. Founded in 2015, we have established ourselves as a trusted partner for businesses seeking innovative technology solutions.

## Our Location
Based in city N, in Country, we serve clients globally with a diverse team of talented professionals.

## Team Size
We have many over 27 employees and 2 managers working across multiple departments:
- Software Development
- UI/UX Design
- Project Management
- Quality Assurance
- DevOps and Infrastructure

## Mission
Our mission is to deliver high-quality, scalable software solutions that drive business growth and innovation for our clients.

## Vision
We envision a future where technology seamlessly integrates with business processes, enabling organizations to achieve their full potential.
services-portfolio.md
# Linx Team - Services & Portfolio

## Services Offered

### Custom Software Development
We build tailored software solutions from scratch, designed specifically for your business needs. Our team handles everything from requirements analysis to deployment and maintenance.

### Web Application Development
Creating responsive, scalable web applications using modern frameworks and best practices. We specialize in both frontend and backend development.

### Mobile Application Development
Developing native and cross-platform mobile applications for iOS and Android that provide excellent user experiences.

### Consulting Services
Our experienced consultants provide strategic guidance on technology architecture, system design, and technology stack selection.

### Staff Augmentation
We provide skilled developers and specialists to augment your existing team, helping you scale quickly without long-term commitments.

### DevOps & Infrastructure
Managing cloud infrastructure, implementing CI/CD pipelines, and optimizing system performance and reliability.

## Industries We Serve

### FinTech
Building secure financial applications with compliance and regulatory requirements.

### E-Commerce
Creating high-performance online stores with payment processing and inventory management.

### Healthcare
Developing HIPAA-compliant healthcare solutions with patient data protection.

### SaaS
Building scalable Software-as-a-Service platforms for various business domains.

### Enterprise Software
Developing complex enterprise solutions for large organizations.

## Project Success
- **100+ Projects Delivered** - Successfully completed projects for clients worldwide
- **Client Retention Rate** - High percentage of clients return for additional projects
- **On-Time Delivery** - Consistent track record of meeting project deadlines
- **Quality Assurance** - Rigorous testing ensures bug-free deployments
technical-expertise.md
# Linx Team - Technical Expertise

## Core Technologies

### Backend Development
- **Laravel** - PHP framework for building robust web applications
- **Node.js** - JavaScript runtime for scalable server-side applications
- **Python** - For data processing and automation
- **PostgreSQL** - Advanced relational database
- **MongoDB** - NoSQL database for flexible data structures

### Frontend Development
- **React** - Modern JavaScript library for building user interfaces
- **Vue.js** - Progressive JavaScript framework
- **TypeScript** - Typed superset of JavaScript
- **Tailwind CSS** - Utility-first CSS framework
- **Next.js** - React framework with server-side rendering

### Cloud & DevOps
- **AWS** - Amazon Web Services cloud platform
- **Docker** - Containerization technology
- **Kubernetes** - Container orchestration
- **CI/CD Pipelines** - Continuous integration and deployment
- **Infrastructure as Code** - Terraform and CloudFormation

### Mobile Development
- **React Native** - Cross-platform mobile development
- **Flutter** - Google's mobile framework
- **iOS and Android** - Native development capabilities

## Best Practices
- Clean Code Architecture
- Test-Driven Development (TDD)
- Agile Methodologies
- Security-First Approach
- Performance Optimization

4.5. Создание VectorStore

Создадим файл src/Classes/PopulateVectorStore.php:

PopulateVectorStore.php
namespace App\demo\src\Classes;
require_once __DIR__ . '/../../../../vendor/autoload.php';

use NeuronAI\RAG\DataLoader\FileDataLoader;
use OpenAI\Factory;

class PopulateVectorStore {
    public static function populate(): void {
        $vectorDir = __DIR__ . '/../../store';
        $storeFile = $vectorDir . '/demo.store';
        $metaFile = $vectorDir . '/demo.meta.json';

        // Ensure directory exists
        if (!is_dir($vectorDir)) {
            mkdir($vectorDir, 0755, true);
        }

        // Clear existing store
        file_put_contents($storeFile, '');

        // Initialize OpenAI client
        $apiKey ='<your-OPENAI_API_KEY-here>';
        if (!is_string($apiKey) || trim($apiKey) === '') {
            throw new \RuntimeException('OpenAI API key not configured. Ensure OPENAI_API_KEY is set.');
        }
        $client = (new Factory())
            ->withApiKey($apiKey)
            ->make();

        $model = 'text-embedding-3-small';

        // Probe expected dimension once
        $expectedDim = 1536;

        // Docs
        $documents = FileDataLoader::for($vectorDir . '/docs')->getDocuments();

        $written = 0;
        // Generate embeddings and write to store
        foreach ($documents as $document) {
            $content = $document->getContent();

            // Get embedding from OpenAI
            try {
                $response = $client->embeddings()->create([
                    'model' => $model,
                    'input' => $content,
                    'dimensions' => $expectedDim,
                ]);

                // SDK v0.12+ exposes embeddings via `$response->embeddings`
                if (isset($response->embeddings[0]->embedding)) {
                    $embedding = $response->embeddings[0]->embedding;
                } else {
                    // Fallback for array casting if SDK shape changes
                    $arr = method_exists($response, 'toArray') ? $response->toArray() : [];
                    if (isset($arr['data'][0]['embedding'])) {
                        $embedding = $arr['data'][0]['embedding'];
                    } else {
                        throw new \RuntimeException('Unable to parse embedding from OpenAI response');
                    }
                }
            } catch (\Throwable $e) {
                throw new \RuntimeException('Failed to generate embedding: ' . $e->getMessage());
            }

            // Normalize and validate embedding
            if (!is_array($embedding)) {
                echo "! Skipped document due to invalid embedding type.\n";
                continue;
            }
            $embedding = array_map(static function ($v) {
                return is_numeric($v) ? (float)$v : 0.0;
            }, $embedding);

            if (count($embedding) !== $expectedDim) {
                echo "! Skipped document due to dimension mismatch (got " . count($embedding) . ", expected $expectedDim).\n";
                continue;
            }

            // Write as JSON line to store file (strict JSONL)
            // FileVectorStore expects all fields at top level
            $jsonLine = json_encode([
                'embedding' => $embedding,
                'content' => $content,
                'sourceType' => $document->getSourceType(),
                'sourceName' => $document->getSourceName(),
                'id' => md5($content),
                'metadata' => [],
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);

            file_put_contents($storeFile, $jsonLine . "\n", FILE_APPEND);
            $written++;

            echo "✓ Added embedding ($written) for: " . $storeFile . " | " . str_replace("\n", ' ', substr(trim($content), 0, 70)) . "...\n";
        }

        // Write metadata file for consistency checks
        $meta = [
            'model' => $model,
            'dimension' => $expectedDim,
            'generatedAt' => date(DATE_ATOM),
            'count' => $written,
        ];
        file_put_contents($metaFile, json_encode($meta, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));

        echo "\n✓ Vector store populated with $written documents (dimension: $expectedDim)\n";
    }
}

Для запуска вызовем index.php со следующим кодом:

<?php

use App\demo\src\Classes\PopulateVectorStore;

require_once __DIR__ . '/src/Classes/PopulateVectorStore.php';

PopulateVectorStore::populate();

В результате мы увидим в окне терминала следующее:

php app/demo/index.php
✓ Added embedding (1) for: /app/demo/src/Commands/../../store/demo.store | # Linx Team - Company Culture & Values  ## Core Values  ### Innovation...
✓ Added embedding (2) for: /app/demo/src/Commands/../../store/demo.store | ## Work Environment  ### Remote-Friendly We offer flexible work arrang...
✓ Added embedding (3) for: /app/demo/src/Commands/../../store/demo.store | ## Benefits  ### Competitive Compensation We offer competitive salarie...
✓ Added embedding (4) for: /app/demo/src/Commands/../../store/demo.store | # Linx Team - Company Overview  ## About Us Linx Team is a leading sof...
✓ Added embedding (5) for: /app/demo/src/Commands/../../store/demo.store | # Linx Team - Services & Portfolio  ## Services Offered  ### Custom So...
✓ Added embedding (6) for: /app/demo/src/Commands/../../store/demo.store | ### DevOps & Infrastructure Managing cloud infrastructure, implementin...
✓ Added embedding (7) for: /app/demo/src/Commands/../../store/demo.store | # Linx Team - Technical Expertise  ## Core Technologies  ### Backend D...
✓ Added embedding (8) for: /app/demo/src/Commands/../../store/demo.store | js** - React framework with server-side rendering  ### Cloud & DevOps ...

✓ Vector store populated with 8 documents (dimension: 1536)

у нас создалось 2 новых файла: demo.meta.json и demo.store

Давайте заглянем в demo.meta.json - тут всё понятно

{
    "model": "text-embedding-3-small",
    "dimension": 1536,
    "generatedAt": "2025-11-15T13:28:53+00:00",
    "count": 8
}

a вот в demo.store мы увидим следующее:

{"embedding":[-0.02263086,-0.007472924,0.029841794,...],"content":"# Linx Team - ... ","sourceType":"files","sourceName":"company-culture.md","id":"28b40662dad319d6f5718881af03283b","metadata":[]}
{"embedding":[-0.023948364,0.009718814,0.06337647,...],"content":"## Work Enviro ... ","sourceType":"files","sourceName":"company-culture.md","id":"b96cd133b0df26e64b95acdad75c87dd","metadata":[]}
{"embedding":[-0.018617272,0.00053190015,0.095444225,...],"content":"## Benefits\n\n### ...","sourceType":"files","sourceName":"company-culture.md","id":"42041e1af0580a58ae07d6523649b1a9","metadata":[]}
{"embedding":[-0.04209091,-0.006933485,0.03687242,...],"content":"# Linx Team - Company ...","sourceType":"files","sourceName":"company-overview.md","id":"8622e016e3fbeccc8dc10bf9a3a851a6","metadata":[]}
{"embedding":[-0.026189856,-0.0032917524,0.05449412,...],"content":"# Linx Team - Services ...","sourceType":"files","sourceName":"services-portfolio.md","id":"acc30742cf9f55588db5275c4feba183","metadata":[]}
{"embedding":[0.0018354928,-0.009989895,0.04954025,...],"content":"### DevOps & ...","sourceType":"files","sourceName":"services-portfolio.md","id":"d32de0136fdd56991a8ab738c49558a2","metadata":[]}
{"embedding":[-0.06210507,-0.015794381,0.038876604,...],"content":"# Linx Team - ...","sourceType":"files","sourceName":"technical-expertise.md","id":"dff18ab4ded5f65154cdd6e81c49318c","metadata":[]}
{"embedding":[-0.02210143,0.016823476,0.038901344,...],"content":"js** - React framework ...","sourceType":"files","sourceName":"technical-expertise.md","id":"a167473a1d4eeac021fbe9bf2ccd0726","metadata":[]}

Каждая строчка здесь это json в формате

{
  "embedding":[...],
  "content":"...",
  "sourceType":"files",
  "sourceName":"company-culture.md",
  "id":"28b40662dad319d6f5718881af03283b",
  "metadata":[]
}

где в embedding находится векторное представление нашего файла, в content - его оригинальный текст и т.д.

Хм… почему же из 4-х документов было создано 8 строк?

Дело в том, документы режутся на чанки (фрагменты) перед эмбеддингом. Вектор хранится не для всего файла целиком, а для каждого фрагмента текста, чтобы поиск был точнее. У нас было 4 исходных файла, но после разбиения получилось 8 фрагментов - значит, каждый файл дал 1–3 чанка (по заголовкам/секциям или по длине).

Почему так делают

  • LLM и эмбеддинги работают лучше, когда им дают короткие, цельные куски (параграф/секция), а не километр полотна.

  • У моделей есть лимиты по токенам; большой документ нельзя нормально “векторизовать” одним кусом.

  • При запросе RAG ищет только релевантные куски, а не весь файл - меньше шума, выше точность.

Почему используем "dimension": 1536? Тут всё просто - мы в примере используем модель text-embedding-3-small от OpenAI, а она всегда выдаёт векторы длиной 1536. Это зашито в саму модель. В других моделях могут быть другие значения.

4.6. Создание ChatBot

Теперь создадим самого агента - src/Commands/ChatBot.php:

ChatBot.php
<?php

namespace App\demo\src\Commands;
require_once __DIR__ . '/../../../../vendor/autoload.php';

use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\OpenAI;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class ChatBot extends RAG
{
    private string $apiKey ='<your-OPENAI_API_KEY-here>';
    private string $model = 'gpt-4o-mini';

    protected function provider(): AIProviderInterface
    {
        if (!$this->apiKey) {
            throw new \Exception('OPENAI_API_KEY environment variable is not set');
        }

        return new OpenAI(
            $this->apiKey,
            $this->model,
        );
    }

    protected function embeddings(): EmbeddingsProviderInterface
    {
        if (!$this->apiKey) {
            throw new \Exception('OPENAI_API_KEY environment variable is not set');
        }

        return new OpenAIEmbeddingsProvider(
            key: $this->apiKey,
            model: 'text-embedding-3-small',
            dimensions: 1536
        );
    }

    protected function vectorStore(): VectorStoreInterface
    {
        $vectorDir = __DIR__ . '/../../store';

        // Ensure the vectors directory exists
        if (!is_dir($vectorDir)) {
            mkdir($vectorDir, 0755, true);
        }

        // Ensure the store file exists with at least one empty line to prevent parsing errors
        $storeFile = $vectorDir . '/demo.store';
        if (!file_exists($storeFile) || filesize($storeFile) === 0) {
            // Create an empty store file - FileVectorStore will populate it when documents are added
            file_put_contents($storeFile, '');
        }

        return new FileVectorStore(
            directory: $vectorDir,
            name: 'demo',
            topK: 3
        );
    }
}

Переделаем немного наш index.php файл.

<?php

use App\demo\src\Classes\PopulateVectorStore;
use App\demo\src\Commands\ChatBot;
use NeuronAI\Chat\Messages\UserMessage;

require_once __DIR__ . '/../../vendor/autoload.php';

// Populate the vector store if it doesn't exist
$storeFile = __DIR__ . '/store/demo.store';
if (!file_exists($storeFile) || filesize($storeFile) === 0) {
    PopulateVectorStore::populate();
    echo "Vector store populated successfully.\n";
} else {
    echo "Vector store found, start handling...\n";
}

$chatBot = ChatBot::make();
$response = $chatBot->chat(
    new UserMessage('How many employees and managers does the company have?')
);

echo "\n" . $response->getContent() . "\n";

Если всё прошло успешно - бот ответит примерно так:

Vector store found, start handling...

The company has over 27 employees and 2 managers, making a total of more than 29 team members.

При желании можно ещё побеспокить нашего чат-бота более сложным вопросом, например:

new UserMessage(
   'How many employees and managers does the company have? ' .
   'Provide links to most relevant documents.'
);

В этот раз ответ будет выглядеть примерно вот так:

The company has over 27 employees and 2 managers.
For more detailed information, you can refer to the following documents:

  1. Company Overview: company-overview.md

  2. Company Culture: company-culture.md

Ну как? Неплохо, да? Наш чат-бот не только нашёл соответствующие документы, передал их в LLM, но вернул правильный ответ, подсчитав сколько всего работников в нашей компании. В добавок ещё и ссылки на нужные документы предоставил!

Важный момент.

Обратите внимание на аргумент topK: 3 - при вызове FileVectorStore

Параметр topK (или иногда пишут top_k) - это просто число, которое определяет, сколько наиболее похожих (релевантных) документов нужно вернуть из векторного стора при поиске. Это означает, что при каждом запросе к векторной базе (similaritySearch() или retrieve()), система выберет 3 ближайших вектора (по косинусному сходству или другому методу) и вернёт их как контекст для LLM.

Итак, после всего окончательная структура проекта выглядит вот так:

/demo/
  ├── store/
  │    ├── docs/
  │    │    ├── company-culture.md
  │    │    ├── company-overview.md
  │    │    ├── services-portfolio.md
  │    │    ├── technical-expertise.md
  │    ├── demo.meta.json
  │    ├── demo.store
  ├── src/
  │    ├── Commands/
  │    │    ├── ChatBot.php
  │    ├── Classes/
  │    │    ├── PopulateVectorStore.php
  └── index.php

5. Что можно улучшить

Это, как вы понимаете, был всего лишь базовый пример. Теперь рассмотрим, что можно улучшить.

  • Хранение данных - вместо файлов можно подключить PostgreSQL, Pinecone или Qdrant.

  • Автоматическое обновление базы - пусть скрипт сам находит и индексирует новые страницы из нашей Wiki или Confluence.

  • Кэширование - чтобы часто задаваемые вопросы не пересчитывались заново.

  • Логирование запросов - полезно для отладки и аналитики.

6. Продвинутая версия: добавляем re-ranking

Если хочется, чтобы бот выбирал ответы точнее, можно добавить re-ranking - пересортировку найденных документов по релевантности.

Neuron AI это позволяет: просто используйте модуль Reranker с моделью типа bge-reranker-base.

И вы удивитесь, насколько “умнее” станет ваш бот.

7. Модульная архитектура: когда и зачем

Если RAG - это просто вспомогательная фича, не нужно плодить модули.
Но если проект растёт, стоит вынести:

  • VectorStoreService

  • EmbeddingPipeline

  • RAGPipeline

  • ChatController

Так вы сможете менять компоненты по отдельности: например, перейти с OpenAI на Ollama или с файлового хранилища на Qdrant - без боли.

8. Итоги

RAG - это не магия, а вполне конкретный паттерн.
Neuron AI даёт PHP-разработчикам возможность наконец-то “поиграть” с нейросетями без пересадки на Python и делает это довольно просто - без серверов, без Docker, без танцев с бубнами.

Да, FileVectorStore - это игрушка, но для локальной демки и старта этого хватает.
Главное - понять принцип, а дальше уже можно внедрить эту идею в вашем любимом фреймворке и двигаться к чему-то более серьёзному.

Комментарии (2)


  1. belonesox
    15.11.2025 18:07

    А вы ведь наверно свою задачу решали…, может вы уже написали готовый экстенш к MediaWiki?


    1. samako Автор
      15.11.2025 18:07

      Пока нет :) Эту задачу решал в отрыве от MediaWiki, скорее как универсальный пример на PHP, а затем мы её имплементировали для нашей базы знаний в Confluence и в GitBook. Но при желании этот же подход несложно упаковать и в экстенш к MediaWiki - там главное правильно вытащить текст страниц и обновлять индексацию, благо сам фреймворк позволяет работать с разными типами документов.