Skip to content

Граф знаний ​

Граф знаний извлекает сущности и отношения при импорте документов и использует ассоциативные связи для поиска дополнительных релевантных фрагментов во время ответов. Его можно использовать совместно с векторным и ключевым поиском для дополнения контекста ответа связями.

Эта функция подходит для материалов с большим количеством связей между людьми, организациями, продуктами или терминами. Включение этой функции увеличивает количество вызовов модели на этапе импорта и требует развертывания Neo4j.

Для хранения графа используется Neo4j с плагином APOC.

Настройка включения ​

Для работы функции графа необходимо одновременное выполнение двух условий:

Глобальный переключатель: переменные окружения Neo4j ​

NEO4J_ENABLE является единственным глобальным переключателем для графа знаний (комментарий в docker-compose.yml гласит: ENABLE_GRAPH_RAG был заменен на NEO4J_ENABLE начиная с v0.1.6, основное приложение на Go его больше не считывает).

НазваниеТипЗначение по умолчаниюОписание
NEO4J_ENABLEstringпусто (выключено)Установите в true для включения графа; initNeo4jClient в internal/container/container.go, постановка задач в очередь и конвейер поиска проверяют это значение
NEO4J_URIstringbolt://neo4j:7687Адрес подключения Neo4j
NEO4J_USERNAMEstringneo4jИмя пользователя
NEO4J_PASSWORDstringpasswordПароль

initNeo4jClient при запуске пытается установить и проверить соединение до 30 раз (интервал 2с); если функция не включена, возвращается nil драйвер, и все методы Neo4jRepository деградируют до no-op (лог NOT SUPPORT RETRIEVE GRAPH). Информационный интерфейс GET /system сообщает "Neo4j" или "Not Enabled" через getGraphDatabaseEngine() (internal/handler/system.go).

Сервис neo4j в docker-compose поставляется с предустановленным APOC: NEO4JLABS_PLUGINS=["apoc"] (запись графа зависит от apoc.merge.node / apoc.merge.relationship, удаление — от apoc.periodic.iterate).

Переключатель на уровне базы знаний: IndexingStrategy + ExtractConfig ​

internal/types/knowledgebase.go:

go
// IsGraphEnabled checks if knowledge graph extraction is enabled.
// Requires both the IndexingStrategy flag and a valid ExtractConfig.
func (kb *KnowledgeBase) IsGraphEnabled() bool {
    return kb != nil && kb.IndexingStrategy.GraphEnabled &&
        kb.ExtractConfig != nil && kb.ExtractConfig.Enabled
}
  • IndexingStrategy.GraphEnabled (internal/types/indexing_strategy.go): Переключатель графа в стратегии индексации базы знаний, по умолчанию false; старое поле ExtractConfig.Enabled при чтении синхронизируется с IndexingStrategy.GraphEnabled (legacy sync в knowledgebase.go).
  • ExtractConfig (internal/types/knowledgebase.go) содержит конфигурацию few-shot для извлечения:
НазваниеТипЗначение по умолчаниюОписание
enabledboolfalseВключено ли извлечение
textstringпустоОригинальный текст примера few-shot
tags[]stringnilНабор тегов типов отношений
nodes[]*GraphNodenilПримеры узлов сущностей (name / attributes)
relations[]*GraphRelationnilПримеры отношений (node1 / node2 / type)
custom_instructionsstringпустоПользовательские инструкции по извлечению для конкретной области (добавляются к системной подсказке, протокол структурированного вывода остается под контролем системы)

Вспомогательный API мастера настройки (internal/handler/initialization.go, маршрут зарегистрирован в internal/router/routes_infra.go, требуется Admin; для API Key требуется возможность manage_models):

  • POST /initialization/extract/text-relation (ExtractTextRelations): Пробный запуск извлечения отношений для текста (≤5000 символов) по выбранным тегам для предварительного просмотра результата;
  • POST /initialization/extract/fabri-text / fabri-tag (FabriText / FabriTag): Позволяет LLM генерировать примеры текста / рекомендованные теги, помогая пользователю быстро создать ExtractConfig.

Процесс извлечения сущностей и отношений (Построение) ​

Запуск и управление задачами ​

После завершения разбора документа, internal/application/service/knowledge_post_process.go на этапе расширения веера выбирает входные данные для извлечения с помощью selectGraphChunks (когда eff.GraphEnabled, graphChunkCount = len(graphChunks)) и вызывает NewChunkExtractTask из internal/application/service/extract.go для постановки каждого чанка в очередь. Правила выбора:

  • Текстовые чанки с основным содержанием участвуют в извлечении; текстовые чанки, содержащие только ссылки на изображения, пропускаются;
  • Если родительский текстовый чанк не имеет основного содержания (типично для страниц отсканированных PDF), используется его дочерний чанк image_ocr, чтобы распознанный текст также попадал в граф;
  • Дочерние чанки image_caption не участвуют, чтобы избежать дублирования с OCR-контентом.
go
func NewChunkExtractTask(...) (bool, error) {
    if strings.ToLower(os.Getenv("NEO4J_ENABLE")) != "true" {
        logger.Warn(ctx, "NEO4J is not enabled, skip chunk extract task")
        return false, nil
    }
    ...
    task := asynq.NewTask(types.TypeChunkExtract, payload,
        asynq.Queue(types.QueueGraph), asynq.MaxRetry(3), asynq.Timeout(30*time.Minute))
    ...
}

Задачи направляются в отдельную очередь asynq QueueGraph, по одному вызову LLM на каждый чанк (в комментариях к коду это называется "самым дорогим этапом расширения веера в конвейере"), с учетом ограничений конкурентности на уровне модели (limiter); отмененные / удаленные / замененные новыми попытками разбора (attemptSuperseded) задачи пропускаются, а счетчик pending_subtasks_count родительской задачи уменьшается.

Выполнение извлечения (ChunkExtractService.Handle) ​

internal/application/service/extract.go:

  1. Загрузка чанка, базы знаний и переопределений на уровне файла ProcessOverrides, расчет действующей конфигурации ExtractConfig с помощью ResolveProcessConfig (если не включено, этап пропускается).
  2. Сборка шаблона структурированной подсказки: системный протокол берется из config.ExtractManager.ExtractGraph (extract.extract_graph в config/config.yaml, многошаговая инструкция, включающая извлечение сущностей + обогащение атрибутов + извлечение отношений), на который накладываются custom_instructions, tags базы знаний и примеры few-shot (Text/Nodes/Relations) из ExtractConfig.
  3. Вызов Chat-модели через chatpipeline.NewExtractor(chatModel, template).Extract(ctx, chunk.Content) (temperature 0.3, max_tokens 8192, отключение thinking; лимит вывода достаточен для большого количества узлов и отношений, чтобы избежать обрезки JSON), результат разбирается Formater.ParseGraph в types.GraphData (internal/types/extract_graph.go):
go
type GraphNode struct {
    Name       string   `json:"name,omitempty"`
    Chunks     []string `json:"chunks,omitempty"`
    Attributes []string `json:"attributes,omitempty"`
}
type GraphRelation struct {
    Node1 string `json:"node1,omitempty"`
    Node2 string `json:"node2,omitempty"`
    Type  string `json:"type,omitempty"`
}
  1. Для каждого узла заполняется node.Chunks = []string{chunk.ID}, затем вызывается graphEngine.AddGraph(ctx, NameSpace{KnowledgeBase, Knowledge}, ...) для записи в Neo4j.
  2. Весь процесс отслеживается SpanTracker (дочерний span postprocess.graph.chunk[i], записывается количество узлов/отношений и примеры).

Хранилище: Neo4j ​

internal/application/repository/retriever/neo4j/repository.go реализует interfaces.RetrieveGraphRepository (AddGraph / DelGraph / SearchNode):

  • Пространство имен как метка: NameSpace{KnowledgeBase, Knowledge} отображается в метки узлов ENTITY<kb_id>, ENTITY<knowledge_id> (дефисы заменяются на подчеркивания), атрибуты узла включают name, kg (knowledge_id), attributes, chunks.
  • Запись выполняется через идемпотентное слияние APOC, поле chunks одноименных сущностей объединяется:
cypher
UNWIND $data AS row
CALL apoc.merge.node(row.labels, {name: row.name, kg: row.knowledge_id}, row.props, {}) YIELD node
SET node.chunks = apoc.coll.union(node.chunks, row.chunks)
  • При удалении знаний / базы знаний (knowledge_delete.go, knowledgebase.go) вызывается DelGraph, использующий apoc.periodic.iterate для пакетного удаления ребер и узлов по 1000 штук за раз.
  • Поиск SearchNode имеет два ограничения, чтобы короткое или часто встречающееся имя сущности не вытянуло весь граф:
  • Только сущности, имеющие хотя бы одну связь, могут служить точками входа, максимум 200 семенных сущностей; сортировка: сначала точные совпадения имен, затем более короткие имена, далее по алфавиту;
  • Возвращается максимум 2000 строк (сущность, отношение), используется та же сортировка, при обрезке сохраняются окрестности семенных сущностей с более высоким рейтингом. При достижении лимита строк записывается предупреждение в лог о том, что результат был обрезан.

Расширение поиска графом (GraphRAG) ​

В традиционном конвейере чата (internal/application/service/chat_pipeline) есть два плагина:

  1. PluginExtractEntity (extract_entity.go, привязан к событию QUERY_UNDERSTAND): Если NEO4J_ENABLE=true, сначала фильтруются базы знаний с ExtractConfig.Enabled (записываются в chatManage.EntityKBIDs / EntityKnowledge), затем с помощью шаблона ExtractManager.ExtractEntity + Chat-модели извлекаются имена сущностей из запроса пользователя и сохраняются в chatManage.Entity.
  2. PluginSearchEntity (search_entity.go, привязан к событию ENTITY_SEARCH): Для каждой базы знаний / файла с включенным графом параллельно вызывается graphRepo.SearchNode — Cypher использует n.name CONTAINS nodeText для нечеткого поиска сущностей и возвращает соседей первого порядка и связи, которые объединяются в chatManage.GraphResult (лимиты поиска см. ниже); затем filterSeenChunk извлекает chunks, связанных с узлами графа (исключая уже найденные векторным поиском), запрашивает оригинальный текст из chunkRepo и добавляет его в набор кандидатов как SearchResult, реализуя дополнение поиска (recall) через граф по схеме "сущность → связанные чанки".

В режиме агента предоставляется инструмент query_knowledge_graph (internal/agent/tools/query_knowledge_graph.go): проверяется наличие конфигурации графа в базах знаний (ExtractConfig.Nodes/Relations не пусты), выполняется параллельный поиск по нескольким базам, дедупликация и сортировка чанков. В вывод включается статус конфигурации графа для каждой базы (список типов сущностей / отношений); для баз без графа возвращаются обычные результаты гибридного поиска. Требование инструмента к способностям агента — all_of: [graph], и он предоставляется модели только в том случае, если в области действия агента есть базы знаний с включенным графом — agent_service.go удаляет его из областей без графов при сборке белого списка инструментов, чтобы модель не вызывала повторно инструмент, возвращающий деградировавшие результаты.

Схемы процессов ​

Процесс построения ​

Процесс запроса ​

Визуализация ​

  • Генерация диаграмм Mermaid: graphBuilder в internal/application/service/graph.go — это реализация интерфейса types.GraphBuilder в памяти (LLM извлекает сущности → извлекает отношения → расчет веса отношений как PMI×0.6 + Strength×0.4 с нормировкой 1-10 → расчет степени узлов → построение графа связей чанков), метод generateKnowledgeGraphDiagram которого использует DFS для поиска компонентов связности и выводит подграф Mermaid graph TD (подсветка высокочастотных сущностей, связи с силой >7 отрисовываются жирными стрелками). Примечание: NewGraphBuilder в данный момент не вызывается при сборке контейнера (в репозитории нет других ссылок), это независимая или устаревшая реализация построения и визуализации графа; диаграммы Mermaid выводятся в лог.
  • Внешний API: У самого графа знаний нет специальной конечной точки REST для визуализации; структурированный вывод инструмента query_knowledge_graph (graph_configs, список результатов) используется фронтендом агента для рендеринга. GET /wiki/graph (wikiHandler.GetGraph) — это собственный интерфейс графа функции Wiki, не связанный с графом отношений сущностей, описанным здесь.
  • Шаблоны подсказок: config/prompt_templates/graph_extraction.yaml содержит шаблоны типа default_extract_entities (перечисление типов сущностей Person/Organization/Location/... и протокол вывода JSON), которые разбираются через extract_entities_prompt_id / extract_relationships_prompt_id в internal/config/config.go в поля Conversation.ExtractEntitiesPrompt / ExtractRelationshipsPrompt и используются вышеупомянутым graphBuilder. Для производственного пути асинхронного извлечения используются шаблоны extract.extract_graph / extract.extract_entity из config.yaml (ExtractManagerConfig).

Версия v0.8.3 · MIT License