Поиск по документации InterSystems с помощью технологий iKnow и iFind

в 9:59, , рубрики: data mining, ifind, iKnow, intersystems, intersystems cache, Блог компании InterSystems, Программирование, Разработка веб-сайтов

image

В СУБД InterSystems Caché есть встроенная технология работы с неструктурированных данными iKnow, а также технология полнотекстового поиска iFind. Решили разобраться с технологией и заодно сделать что-то полезное. В итоге получился DocSearch — Веб приложение для поиска по документации InterSystems, с использованием технологий iKnow и iFind.

Как устроена документация в Caché

Документация в Caché сделана на технологии Docbook. Для доступа к документации поставляется веб-интерфейс (в том числе с поиском, который не использует ни iFind ни iKnow). Собственно данные статей документации лежат в классах Caché, что открывает возможность самостоятельно осуществлять запросы к этим данным, ну и соответственно возможность написать свою утилиту поиска.

Что такое iKnow и iFind:

Intersystems iKnow это средство анализа неструктурированных данных, предоставляющее доступ к данным путем индексирования содержащихся в тексте предложений и сущностей. Для начала анализа, необходимо создать домен — хранилище неструктурированных данных, и загрузить в него текст. Процесс создания домена хорошо описан здесь и здесь. Об основных способах использования iKnow написано тут, также я рекомендовал бы вам эту статью.

Технология iFind это модуль СУБД Caché для выполнения операций полнотекстового поиска по данным классов Caché. iFind использует многие функции iKnow для обеспечения интеллектуального текстового поиска. Чтобы использовать iFind в запросах, необходимо описать в классе Caché специальный iFind индекс.

Существует три вида индексов iFind, каждый вид индекса предоставляет все функции предыдущего вида, плюс дополнительные функции:

  • Основной индекс (%iFind.Index.Basic): поддерживает поиск слов и словосочетаний.
  • Семантический индекс (%iFind.Index.Semantic): поддерживает поиск объектов iKnow.
  • Аналитический индекс (%iFind.Index.Analytic): поддерживает все функции iKnow в семантическом индексе, а также информацию о пути и близости слов.

Так как классы документации хранятся в отдельной области, то для того чтобы сделать классы доступными в нашей области, установщик производит маппинг пакетов и глобалов.

Код для маппинга в установщике

XData Install [ XMLNamespace = INSTALLER ]
{
<Manifest>
// Указываем название области
<IfNotDef Var="Namespace">
<Var Name="Namespace" Value="DOCSEARCH"/>
<Log Text="Set namespace to ${Namespace}" Level="0"/>
</IfNotDef>

// Проверяем существует ли такая область
<If Condition='(##class(Config.Namespaces).Exists("${Namespace}")=1)'>
<Log Text="Namespace ${Namespace} already exists" Level="0"/>
</If>

// Создаем область
<If Condition='(##class(Config.Namespaces).Exists("${Namespace}")=0)'>
<Log Text="Creating namespace ${Namespace}" Level="0"/>
	
// Создаем базу данных
<Namespace Name="${Namespace}" Create="yes" Code="${Namespace}" Ensemble="" Data="${Namespace}">
<Log Text="Creating database ${Namespace}" Level="0"/>

// Маппируем указанные классы и глобалы в новую область
<Configuration>
<Database Name="${Namespace}" Dir="${MGRDIR}/${Namespace}" Create="yes" MountRequired="false" Resource="%DB_${Namespace}" PublicPermissions="RW" MountAtStartup="false"/>
<Log Text="Mapping DOCBOOK to ${Namespace}" Level="0"/>
<GlobalMapping Global="Cache*" From="DOCBOOK" Collation="5"/>
<GlobalMapping Global="D*" From="DOCBOOK" Collation="5"/>
<GlobalMapping Global="XML*" From="DOCBOOK" Collation="5"/>
<ClassMapping Package="DocBook" From="DOCBOOK"/>
<ClassMapping Package="DocBook.UI" From="DOCBOOK"/>
<ClassMapping Package="csp" From="DOCBOOK"/>
</Configuration>

<Log Text="End creating database ${Namespace}" Level="0"/>
</Namespace>
<Log Text="End creating namespace ${Namespace}" Level="0"/>
</If>

 </Manifest>
}

Домен, который нужен нам для работы iKnow, строится по таблице содержащей документацию. Так как источником данных является таблица, будем использовать SQL.Lister. Поле content содержит текст документации, значит укажем его как поле данных. Остальные поля укажем в метаданных.

Код создания домена в установщике
ClassMethod Domain(ByRef pVars, pLogLevel As %String, tInstaller As %Installer.Installer) As %Status
{
	#Include %IKInclude
	#Include %IKPublic
	set ns = $Namespace
	znspace "DOCSEARCH"
	// Создание домена или открытие если он существует
	set dname="DocSearch" 
   	if (##class(%iKnow.Domain).Exists(dname)=1){
	   	write "The ",dname," domain already exists",!
		zn ns
		quit
        }
  	else {	 
  		write "The ",dname," domain does not exist",!
       	set domoref=##class(%iKnow.Domain).%New(dname)
       	do domoref.%Save()
        }
   	set domId=domoref.Id
   	// Lister используется для поиска источников, соответствующих записям в результатах запроса
  	set flister=##class(%iKnow.Source.SQL.Lister).%New(domId)
  	set myloader=##class(%iKnow.Source.Loader).%New(domId)
  	// Построение запроса
	set myquery="SELECT id, docKey, title, bookKey, bookTitle, content, textKey FROM SQLUser.DocBook"
 	set idfld="id"
 	set grpfld="id"
 	// Указываем поля данных и метаданных
  	set dataflds=$LB("content")
  	set metaflds=$LB("docKey", "title", "bookKey", "bookTitle", "textKey")
        //Занесем все данные в Lister
  	set stat=flister.AddListToBatch(myquery,idfld,grpfld,dataflds,metaflds)
        if stat '= 1 {write "The lister failed: ",$System.Status.DisplayError(stat) quit }
        //Запускаем процесс анализа
        set stat=myloader.ProcessBatch()
        if stat '= 1 {
	      quit 
	       }
        set numSrcD=##class(%iKnow.Queries.SourceQAPI).GetCountByDomain(domId)
        write "Done",!
        write "Domain cointains ",numSrcD," source(s)",!
        zn ns
        quit
}

Для поиска по документации мы используем индекс %iFind.Index.Analytic:

Index contentInd On (content) As %iFind.Index.Analytic(LANGUAGE = "en", LOWER = 1, RANKERCLASS = "%iFind.Rank.Analytic");

Где contentInd — название индекса, content — название поля для которого создаем индекс.
Параметр LANGUAGE = «en», указывает язык на котором написан текст
Параметром LOWER = 1, задает нечувствительность к регистру
Параметр RANKERCLASS = "%iFind.Rank.Analytic", позволяет использовать алгоритм ранжирования результатов TF-IDF

После добавления и построения такого индекса, его можно использовать, например в SQL запросах. Общий синтаксис использования iFind в SQL:

SELECT * FROM TABLE WHERE %ID %FIND search_index(indexname,'search_items',search_option)

После создания индекса %iFind.Index.Analytic с такими параметрами генерируются несколько SQL процедур вида — [Название таблицы]_[Название индекса]Название процедуры

В нашем проекте мы используем две из них:

  • DocBook_contentIndRank — Возвращает результат алгоритма ранжирования TF-IDF для запроса
    Синтаксис имеет вид:

    SELECT DocBook_contentIndRank(%ID, ‘SearchString’, ‘SearchOption’) Rank FROM DocBook WHERE %ID %FIND search_index(contentInd,‘SearchString’, ‘SearchOption’)
  • DocBook_contentIndHighlight — возвращает результаты поиска, где искомые слова обрамлены в указанный тег
    SELECT DocBook_contentIndHighlight(%ID, ‘SearchString’, ‘SearchOption’,’Tags’) Text FROM DocBook WHERE %ID %FIND search_index(contentInd,‘SearchString’, ‘SearchOption’)

Про использование этих процедур я расскажу чуть ниже.

Что в итоге получилось:

  1. Автозаполнение в строке поиска

    Когда вы вводите текст в строке поиска, предлагаются возможные варианты запросов, чтобы помочь быстрее найти нужную информацию. Эти подсказки создаются на основе того слова(или начальной части слова, если ввод слова не закончен) которое вы ввели и пользователю демонстрируются десять наиболее похожих слов или словосочетаний.
    Происходит этот процесс с помощью iKnow, метода %iKnow.Queries.Entity.GetSimilar

    image

  2. Нечеткий поиск

    Технология iFind поддерживает нечеткий поиск, для нахождения слов, почти соответствующих строке поиска. Реализуется с помощью сравнения расстояния Левенштейна между двумя словами. Расстояние Левенштейна — это минимальное количество односимвольных изменений (вставки, удаления или замены), необходимые для изменения одного слова в другое. Может быть использовано для исправления опечаток, небольших вариаций в письменной форме, различных грамматических форм (единственное и множественное число).

    В SQL запросах iFind, за использование нечеткого поиска отвечает параметр search_option.
    Значение search_option = 3, означает расстояния Левенштейна равное двум.
    Для задания расстояния Левенштейна равное n, следует указать значение search_option = ‘3:n’
    В поиске по документации используется расстояния Левенштейна равное единице, продемонстрируем как это работает:

    Наберем в поиске слово ifind:

    image

    Попробуем произвести нечеткий поиск, например слово с опечаткой — ifindd. Как мы видим поиск исправил опечатку и нашел нужные статьи.

    image

  3. Сложные запросы

    Благодаря тому, что iFind поддерживает сложные запросы с применением скобок и операторов AND OR NOT, мы реализовали расширенный поиск. В поиске можно указать: слово, словосочетания, любое из нескольких слов, либо не содержащие некоторые слова. Поля можно заполнять как одно или несколько, так и все сразу.

    Например, найдем статьи содержащие слово iknow, словосочетание rest api и содержащие любое из слов domain или UI.

    image

    Видим, что таких статей две:

    image

    Заметим, что во второй статье упоминается Swagger UI, можно добавить в запрос, поиск статьей, не содержащих слово Swagger

    image

    В итоге найдена только одна статья:

    image

  4. Подсветка результатов поиска

    Как уже говорилось выше, использование iFind индекса, создает процедуру DocBook_contentIndHighlight. Используя:

    SELECT DocBook_contentIndHighlight(%ID, 'search_items', '0', '<span class=""Illumination"">', 0) Text FROM DocBook

    Получаем искомый текст обрамленный в тег

    <span class="Illumination">

    Это позволяет на фронтенде визуально выделять результаты поиска.

    image

  5. Алгоритм ранжирования результатов

    iFind поддерживает возможность ранжирования результатов по алгоритму TF-IDF. Мера TF-IDF часто используется в задачах анализа текстов и информационного поиска, например, как один из критериев релевантности документа поисковому запросу.

    В результате SQL запроса, поле Rank будет содержать вес слова, который пропорционален количеству употребления этого слова в статье, и обратно пропорционален частоте употребления слова в других статьях.

    SELECT DocBook_contentIndRank(%ID, ‘SearchString’, ‘SearchOption’) Rank FROM DocBook WHERE %ID %FIND search_index(contentInd,‘SearchString’, ‘SearchOption’)

  6. Интеграция с официальным поиском по документации

    После установки, в официальный поиск по документации добавляется кнопка “Search using iFind”.

    image

    Если заполнено поле Search words, то после нажатия на “Search using iFind”, будет выполнен переход на страницу с результатами поиска для введенного запроса.

    Если поле не заполнено, то будет выполнен переход на начальную страницу нового поиска.

Установка

  1. Загрузите из последнего релиза с страницы релизов, файл Installer.xml
  2. Импортируйте загруженный файл Installer.xml в область %SYS, скомпилирйте.
  3. В терминале в области %SYS введите следующую команду:
    do ##class(Docsearch.Installer).setup(.pVars)

    Процесс занимает около 15-30 минут из-за процесса построения домена.

После этого, поиск доступен по адресу localhost:[порт]/csp/docsearch/index.html

Демо

Онлайн-демо поиска доступно здесь

Заключение

В этом проекте демонстрируются интересные и полезные возможности технологий iFind и iKnow, благодаря которым поиск становиться релевантнее.
Критика, замечания, предложения — приветствуются.
Весь исходный код с инсталлятором и инструкцией по установке выложен на гитхаб

Автор: InterSystems

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js