Граф знаний
Граф знаний извлекает сущности и отношения при импорте документов и использует ассоциативные связи для поиска дополнительных релевантных фрагментов во время ответов. Его можно использовать совместно с векторным и ключевым поиском для дополнения контекста ответа связями.
Эта функция подходит для материалов с большим количеством связей между людьми, организациями, продуктами или терминами. Включение этой функции увеличивает количество вызовов модели на этапе импорта и требует развертывания Neo4j.
Для хранения графа используется Neo4j с плагином APOC.
Настройка включения
Для работы функции графа необходимо одновременное выполнение двух условий:
Глобальный переключатель: переменные окружения Neo4j
NEO4J_ENABLE является единственным глобальным переключателем для графа знаний (комментарий в docker-compose.yml гласит: ENABLE_GRAPH_RAG был заменен на NEO4J_ENABLE начиная с v0.1.6, основное приложение на Go его больше не считывает).
| Название | Тип | Значение по умолчанию | Описание |
|---|---|---|---|
NEO4J_ENABLE | string | пусто (выключено) | Установите в true для включения графа; initNeo4jClient в internal/container/container.go, постановка задач в очередь и конвейер поиска проверяют это значение |
NEO4J_URI | string | bolt://neo4j:7687 | Адрес подключения Neo4j |
NEO4J_USERNAME | string | neo4j | Имя пользователя |
NEO4J_PASSWORD | string | password | Пароль |
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:
// 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 для извлечения:
| Название | Тип | Значение по умолчанию | Описание |
|---|---|---|---|
enabled | bool | false | Включено ли извлечение |
text | string | пусто | Оригинальный текст примера few-shot |
tags | []string | nil | Набор тегов типов отношений |
nodes | []*GraphNode | nil | Примеры узлов сущностей (name / attributes) |
relations | []*GraphRelation | nil | Примеры отношений (node1 / node2 / type) |
custom_instructions | string | пусто | Пользовательские инструкции по извлечению для конкретной области (добавляются к системной подсказке, протокол структурированного вывода остается под контролем системы) |
Вспомогательный 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-контентом.
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:
- Загрузка чанка, базы знаний и переопределений на уровне файла
ProcessOverrides, расчет действующей конфигурацииExtractConfigс помощьюResolveProcessConfig(если не включено, этап пропускается). - Сборка шаблона структурированной подсказки: системный протокол берется из
config.ExtractManager.ExtractGraph(extract.extract_graphвconfig/config.yaml, многошаговая инструкция, включающая извлечение сущностей + обогащение атрибутов + извлечение отношений), на который накладываютсяcustom_instructions,tagsбазы знаний и примеры few-shot (Text/Nodes/Relations) изExtractConfig. - Вызов 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):
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"`
}- Для каждого узла заполняется
node.Chunks = []string{chunk.ID}, затем вызываетсяgraphEngine.AddGraph(ctx, NameSpace{KnowledgeBase, Knowledge}, ...)для записи в Neo4j. - Весь процесс отслеживается 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одноименных сущностей объединяется:
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) есть два плагина:
- PluginExtractEntity (
extract_entity.go, привязан к событиюQUERY_UNDERSTAND): ЕслиNEO4J_ENABLE=true, сначала фильтруются базы знаний сExtractConfig.Enabled(записываются вchatManage.EntityKBIDs/EntityKnowledge), затем с помощью шаблонаExtractManager.ExtractEntity+ Chat-модели извлекаются имена сущностей из запроса пользователя и сохраняются вchatManage.Entity. - 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 для поиска компонентов связности и выводит подграф Mermaidgraph 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).
