Поле semantic_text и inference-эндпоинты
В статье ELSER и разреженные векторы для семантики мы прошли весь путь вручную: создали inference-эндпоинт, написали ingest pipeline, передавали ?pipeline=elser-pipeline при каждой индексации. Три отдельных конфигурационных шага, которые нужно поддерживать согласованными. Поле semantic_text сокращает их до одного.
С semantic_text вы объявляете тип поля — и Elasticsearch берёт на себя всё остальное: вызов inference-эндпоинта при индексации, разбивку длинного текста на фрагменты и хранение векторов. Никакого _ingest/pipeline, никакого ?pipeline=....
Inference-эндпоинт: единственное, что нужно создать
Перед созданием индекса нужен inference-эндпоинт — точно так же, как при ручной работе с ELSER. Если вы читали предыдущую статью последовательно, эндпоинт my-elser-endpoint у вас уже есть.
Xорошая новость: ES 8 поставляется с двумя встроенными эндпоинтами, которые не нужно создавать вручную:
.elser-2-elasticsearch— ELSER v2, разреженные эмбеддинги. Оптимизирован для английского, с другими языками тоже работает..multilingual-e5-small-elasticsearch— плотные эмбеддинги, явная поддержка нескольких языков, включая русский.
Для быстрого старта можно указать .elser-2-elasticsearch прямо в маппинге, не создавая ничего заранее. Если нужны кастомные настройки аллокации — создайте эндпоинт явно, как описано в статье про ELSER.
Маппинг: одна строчка вместо трёх шагов
PUT /articles
{
"mappings": {
"properties": {
"title": { "type": "text" },
"content": {
"type": "semantic_text",
"inference_id": ".elser-2-elasticsearch"
}
}
}
}Поле content теперь «знает», через какой эндпоинт генерировать эмбеддинги. ES сохраняет эту связь в маппинге и использует её при каждой индексации.
> Если inference_id не указан — по умолчанию используется .elser-2-elasticsearch. Удобно для прототипов, где хочется как можно меньше конфигурации.
Индексирование: просто отправьте документ
POST /articles/_doc
{
"title": "Шумоподавляющие наушники для путешественников",
"content": "Если вы часто летаете, наушники с активным шумоподавлением значительно снизят усталость в долгих перелётах. Технология ANC глушит монотонный гул двигателей, позволяя слушать музыку на меньшей громкости. При выборе обращайте внимание на автономность и складную конструкцию — это важно для перевозки в ручной клади."
}Никакого ?pipeline=.... При получении этого запроса Elasticsearch:
1. Берёт текст поля content.
2. Разбивает его на чанки (об этом — следующий раздел).
3. Передаёт каждый чанк в ELSER-эндпоинт.
4. Сохраняет полученные векторы внутри документа.
Вставка происходит синхронно — ответ придёт после того, как эмбеддинги будут сгенерированы. На больших объёмах используйте Массовая загрузка данных через Bulk API параллельными батчами — иначе inference-эндпоинт станет узким местом.
flowchart LR
subgraph manual["Ручной подход (ELSER)"]
direction TB
M1["① PUT /_inference"] --> M2["② PUT /_ingest/pipeline"] --> M3["③ PUT /index sparse_vector"] --> M4["④ POST /_doc?pipeline=..."] --> M5["Векторы в поле sparse_vector"]
end
subgraph smart["semantic_text"]
direction TB
S1["① PUT /index semantic_text + inference_id"] --> S2["② POST /_doc обычный"] --> S3["Авточанкинг + векторы сохранены"]
endАвтоматический чанкинг — главное отличие от ручного ELSER
В статье про ELSER упоминалось критичное ограничение: модель обрабатывает только первые 512 токенов текста. Длинные документы нужно нарезать вручную — иначе всё, что дальше этой границы, просто теряется и не будет найдено при поиске.
semantic_text решает эту проблему автоматически. По умолчанию (начиная с ES 8.16) текст делится на предложения, которые группируются в секции по 250 слов с перекрытием в одно предложение. Каждая секция обрабатывается независимо и получает свой вектор.
Для документа из примера выше ES создаст примерно такие чанки:
- Чанк 1: «Если вы часто летаете, наушники с активным шумоподавлением значительно снизят усталость в долгих перелётах.»
- Чанк 2: «Технология ANC глушит монотонный гул двигателей, позволяя слушать музыку на меньшей громкости.»
- Чанк 3: «При выборе обращайте внимание на автономность и складную конструкцию…»
При поиске ES сравнивает вектор запроса со всеми чанками всех документов и возвращает лучшие совпадения — даже если нужный фрагмент находится в середине длинного текста.
Параметры чанкинга можно переопределить прямо в маппинге:
"content": {
"type": "semantic_text",
"inference_id": ".elser-2-elasticsearch",
"chunking_settings": {
"type": "sentence",
"max_chunk_size": 150,
"sentence_overlap": 1
}
}Доступные значения type: "sentence" — по предложениям (по умолчанию), "word" — по словам, "none" — без разбивки (для коротких текстов или если вы нарезаете сами, передавая массив строк вместо одной строки).
Запрос semantic
GET /articles/_search
{
"query": {
"semantic": {
"field": "content",
"query": "как не уставать в дальнем перелёте"
}
}
}ES берёт строку "query", прогоняет её через тот же ELSER-эндпоинт, сравнивает результирующий вектор со всеми чанками поля content и возвращает документы по релевантности. Документ о наушниках окажется в выдаче — несмотря на то что в запросе нет слов «наушники» или «ANC».
semantic-запрос хорошо комбинируется с фильтрами через bool:
GET /articles/_search
{
"query": {
"bool": {
"must": [
{
"semantic": {
"field": "content",
"query": "как не уставать в дальнем перелёте"
}
}
],
"filter": [
{ "term": { "category": "travel" } }
]
}
}
}А в гибридном поиске semantic объединяется с match через Гибридный поиск: retrievers и RRF — тогда один запрос охватывает и точные совпадения по ключевым словам, и семантику.
search_inference_id: разные модели для индексации и поиска
Опциональный параметр маппинга — отдельный эндпоинт только для векторизации поискового запроса:
"content": {
"type": "semantic_text",
"inference_id": ".elser-2-elasticsearch",
"search_inference_id": "my-fast-elser-endpoint"
}Это позволяет, например, индексировать через качественную модель, а запросы обрабатывать через более лёгкую и быструю — что снижает latency поиска. Единственное условие: оба эндпоинта должны генерировать совместимые векторы (sparse к sparse, dense к dense), иначе сравнение не даст смысла.
Ограничения
- Поле нельзя использовать внутри
nested-полей и в Cross-Cluster Search (поиск по нескольким кластерам). - После создания индекса изменить
inference_idполя нельзя — только reindex в новый индекс с обновлённым маппингом. - Синхронная генерация эмбеддингов замедляет вставку: на высоких нагрузках нужно планировать дополнительные ML-ноды.
- Требует индексов, созданных на ES 8.11+; Generally Available с версии 8.18.
semantic_text против ручного подхода: когда что выбирать
Простое правило: semantic_text — это ELSER в автоматическом режиме. Берите его по умолчанию. Переходите к ручному подходу через sparse_vector + pipeline только тогда, когда нужен нестандартный контроль: например, дополнительная нормализация текста прямо в пайплайне или хранение эмбеддингов в отдельном индексе по специфической схеме.
См. также
- ELSER и разреженные векторы для семантики — ручная настройка инференса и
sparse_vector: что происходит «под капотом» уsemantic_text - Векторный поиск: поле dense_vector и запрос kNN — альтернативный подход с плотными векторами и собственными эмбеддингами
- Гибридный поиск: retrievers и RRF — объединение
semantic_textс BM25-поиском через RRF - Маппинг: явный, динамический и шаблоны индексов — управление схемой полей индекса
- Массовая загрузка данных через Bulk API — эффективная индексация при большом потоке документов
- Составные запросы: bool и комбинирование условий — фильтры и комбинирование запросов