🌟 Введение: Что такое граф знаний?
Простая аналогия: Представьте социальную сеть. У вас есть профиль (узел), у вас есть друзья (связи), у друзей есть свои профили и связи. Граф знаний — это такая же сеть, но для любых понятий, фактов и идей.
Технически: Граф знаний — это база данных, где информация хранится не в таблицах, а в виде узлов (сущностей) и связей между ними.
Зачем ИИ-агенту граф знаний:
- 📚 Понимание контекста — агент видит связи между понятиями
- 🔍 Быстрый поиск — находить информацию по связям, а не по ключевым словам
- 🤔 Логические выводы — "если A дружит с B, а B дружит с C, то A и C могут знать друг друга"
🚀 Шаг 1: Установка графовой базы данных (Neo4j)
Цель шага: Установить и запустить Neo4j — самую популярную графовую базу данных.
Что такое Neo4j:
Это база данных, которая специализируется на хранении и обработке графов. У неё есть свой язык запросов — Cypher, который похож на английский язык.
Установка через Docker (самый простой способ):
bash
# 1. Установите Docker (если ещё нет)
# Скачайте с https://www.docker.com/products/docker-desktop/
# 2. Запустите Neo4j в контейнере
docker run \
--name neo4j \
-p 7474:7474 -p 7687:7687 \
-d \
--env NEO4J_AUTH=neo4j/password123 \
neo4j:latest
Объяснение параметров:
- -p 7474:7474 — веб-интерфейс (браузер)
- -p 7687:7687 — соединение для программ
- -d — запустить в фоне
- --env NEO4J_AUTH — логин и пароль
Проверка установки:
- Введите логин: neo4j
- Введите пароль: password123
Что должно получиться:
✅ Веб-интерфейс Neo4j Browser открылся
✅ Видите приглашение для ввода команд Cypher
✅ Система готова к созданию первого графа
На что обратить внимание:
- Если порт 7474 занят, измените номер порта
- Docker должен быть запущен
- Первый запуск может занять 1-2 минуты
📝 Шаг 2: Первый граф знаний — социальная сеть
Цель шага: Создать простой граф в Neo4j Browser и понять базовые концепции.
Основные понятия:
- Узел (Node) — любая сущность (человек, город, товар)
- Связь (Relationship) — как узлы связаны (дружит, живёт, покупал)
- Свойства (Properties) — характеристики узлов и связей (имя, возраст, дата)
Создаём граф друзей:
В веб-интерфейсе Neo4j Browser введите:
cypher
// 1. Создаём узлы (людей)
CREATE (иван:Человек {имя: 'Иван', возраст: 25, город: 'Москва'})
CREATE (анна:Человек {имя: 'Анна', возраст: 23, город: 'Москва'})
CREATE (петр:Человек {имя: 'Пётр', возраст: 30, город: 'Санкт-Петербург'})
CREATE (мария:Человек {имя: 'Мария', возраст: 28, город: 'Казань'})
// 2. Создаём связи ДРУЖИТ
CREATE (иван)-[:ДРУЖИТ_С {с: '2020-01-15'}]->(анна)
CREATE (иван)-[:ДРУЖИТ_С {с: '2019-05-20'}]->(петр)
CREATE (анна)-[:ДРУЖИТ_С {с: '2021-03-10'}]->(мария)
CREATE (петр)-[:ДРУЖИТ_С {с: '2018-11-05'}]->(мария)
Объяснение синтаксиса:
- (иван:Человек {...}) — создать узел с меткой "Человек" и свойствами
- -[:ДРУЖИТ_С {...}]-> — создать направленную связь с свойствами
- {} — свойства в формате ключ-значение
Визуализируем граф:
В том же окне введите:
cypher
// Показать весь граф
MATCH (n) RETURN n
Что происходит:
- MATCH (n) — найти все узлы
- RETURN n — вернуть (показать) их
Что должно получиться:
✅ На экране появляются 4 кружочка (узлы)
✅ Видны стрелочки между ними (связи)
✅ Можно кликнуть на узел — увидеть его свойства
На что обратить внимание:
- Связи могут быть направленными (→) или ненаправленными (—)
- У каждого узла может быть несколько меток
- Свойства можно добавлять и изменять
🔍 Шаг 3: Запросы к графу — поиск информации
Цель шага: Научиться "спрашивать" граф и получать ответы.
Базовые запросы:
1. Найти всех людей:
cypher
MATCH (ч:Человек)
RETURN ч.имя, ч.возраст
2. Найти друзей Ивана:
cypher
MATCH (иван:Человек {имя: 'Иван'})-[:ДРУЖИТ_С]->(друг)
RETURN друг.имя
3. Найти друзей друзей Ивана (друзья второго уровня):
cypher
MATCH (иван:Человек {имя: 'Иван'})-[:ДРУЖИТ_С*2]->(другДруга)
RETURN другДруга.имя
4. Кто живёт в Москве:
cypher
MATCH (ч:Человек {город: 'Москва'})
RETURN ч.имя, ч.возраст
Практическое задание:
Создайте новый узел "Ольга" и свяжите её с кем-то. Затем найдите всех, с кем она косвенно связана.
Что должно получиться:
✅ Умение писать простые запросы Cypher
✅ Понимание, как извлекать информацию из графа
✅ Видите разницу между прямыми и косвенными связями
🧠 Шаг 4: Граф знаний для ИИ-агента — рекомендации фильмов
Цель шага: Создать полезный граф знаний для реальной задачи.
Сценарий: ИИ-агент рекомендует фильмы на основе предпочтений пользователей.
Создаём граф фильмов:
cypher
// Очищаем предыдущие данные (необязательно)
MATCH (n) DETACH DELETE n;
// 1. Создаём узлы фильмов
CREATE (интерстеллар:Фильм {
название: 'Интерстеллар',
год: 2014,
рейтинг: 8.6,
описание: 'Фантастика о путешествии через червоточину'
})
CREATE (матрица:Фильм {
название: 'Матрица',
год: 1999,
рейтинг: 8.7
})
CREATE (побегИзШоушенка:Фильм {
название: 'Побег из Шоушенка',
год: 1994,
рейтинг: 9.3
})
// 2. Создаём узлы жанров
CREATE (фантастика:Жанр {название: 'Фантастика'})
CREATE (драма:Жанр {название: 'Драма'})
CREATE (триллер:Жанр {название: 'Триллер'})
// 3. Создаём узлы пользователей
CREATE (иван:Пользователь {
имя: 'Иван',
возраст: 25,
предпочтения: 'Фантастика, экшен'
})
CREATE (анна:Пользователь {
имя: 'Анна',
возраст: 23,
предпочтения: 'Драма, мелодрама'
})
// 4. Связываем фильмы с жанрами
CREATE (интерстеллар)-[:В_ЖАНРЕ]->(фантастика)
CREATE (интерстеллар)-[:В_ЖАНРЕ]->(драма)
CREATE (матрица)-[:В_ЖАНРЕ]->(фантастика)
CREATE (побегИзШоушенка)-[:В_ЖАНРЕ]->(драма)
CREATE (побегИзШоушенка)-[:В_ЖАНРЕ]->(триллер)
// 5. Связываем пользователей с просмотренными фильмами
CREATE (иван)-[:СМОТРЕЛ {оценка: 9, дата: '2024-01-10'}]->(интерстеллар)
CREATE (иван)-[:СМОТРЕЛ {оценка: 8, дата: '2024-01-05'}]->(матрица)
CREATE (анна)-[:СМОТРЕЛ {оценка: 10, дата: '2024-01-12'}]->(побегИзШоушенка)
// 6. Связываем пользователей (кто с кем дружит)
CREATE (иван)-[:ДРУЖИТ_С]->(анна)
Полезные запросы для рекомендаций:
1. Какие фильмы смотрел Иван:
cypher
MATCH (иван:Пользователь {имя: 'Иван'})-[:СМОТРЕЛ]->(фильм)
RETURN фильм.название, фильм.рейтинг
2. Какие жанры нравятся Ивану (на основе просмотренных):
cypher
MATCH (иван:Пользователь {имя: 'Иван'})-[:СМОТРЕЛ]->(фильм)-[:В_ЖАНРЕ]->(жанр)
RETURN жанр.название, COUNT(*) as просмотров
ORDER BY просмотров DESC
3. Что смотрели друзья Ивана (социальные рекомендации):
cypher
MATCH (иван:Пользователь {имя: 'Иван'})-[:ДРУЖИТ_С]->(друг)-[:СМОТРЕЛ]->(фильм)
WHERE NOT (иван)-[:СМОТРЕЛ]->(фильм)
RETURN фильм.название, фильм.рейтинг, друг.имя
ORDER BY фильм.рейтинг DESC
Что должно получиться:
✅ Граф для рекомендательной системы
✅ Понимание, как строить связи между разными типами сущностей
✅ Основа для ИИ-агента, который рекомендует фильмы
🐍 Шаг 5: Работа с графом из Python
Цель шага: Научиться управлять графом знаний из программы на Python.
Установка драйвера Neo4j для Python:
bash
pip install neo4j
Подключаемся к Neo4j из Python:
python
# neo4j_basic.py
from neo4j import GraphDatabase
# Подключение к базе данных
URI = "bolt://localhost:7687"
USER = "neo4j"
PASSWORD = "password123"
class Neo4jConnection:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def close(self):
self.driver.close()
def run_query(self, query, parameters=None):
with self.driver.session() as session:
result = session.run(query, parameters)
return list(result)
# Создаём подключение
conn = Neo4jConnection(URI, USER, PASSWORD)
# Пример 1: Простой запрос
query = "MATCH (ф:Фильм) RETURN ф.название, ф.рейтинг"
films = conn.run_query(query)
print("🎬 Все фильмы в базе:")
for film in films:
print(f" - {film['ф.название']} (рейтинг: {film['ф.рейтинг']})")
# Пример 2: Запрос с параметрами
query = """
MATCH (п:Пользователь {имя: $name})-[:СМОТРЕЛ]->(ф:Фильм)
RETURN ф.название, ф.рейтинг
"""
watched = conn.run_query(query, {"name": "Иван"})
print("\n📺 Что смотрел Иван:")
for film in watched:
print(f" - {film['ф.название']}")
# Закрываем подключение
conn.close()
Создаём ИИ-агент для рекомендаций:
python
# movie_recommender.py
from neo4j import GraphDatabase
class MovieRecommender:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def close(self):
self.driver.close()
def recommend_by_genre(self, genre_name, limit=5):
"""Рекомендовать фильмы по жанру"""
query = """
MATCH (ф:Фильм)-[:В_ЖАНРЕ]->(ж:Жанр {название: $genre})
RETURN ф.название, ф.рейтинг
ORDER BY ф.рейтинг DESC
LIMIT $limit
"""
with self.driver.session() as session:
result = session.run(query, genre=genre_name, limit=limit)
return [{"фильм": record["ф.название"], "рейтинг": record["ф.рейтинг"]} for record in result]
def recommend_for_user(self, user_name):
"""Рекомендовать фильмы для конкретного пользователя"""
query = """
// 1. Находим пользователя
MATCH (пользователь:Пользователь {имя: $name})
// 2. Какие жанры ему нравятся (по просмотренным)
MATCH (пользователь)-[:СМОТРЕЛ]->(просмотренный:Фильм)-[:В_ЖАНРЕ]->(любимый_жанр:Жанр)
// 3. Находим фильмы в этих жанрах, которые он не смотрел
MATCH (рекомендация:Фильм)-[:В_ЖАНРЕ]->(любимый_жанр)
WHERE NOT (пользователь)-[:СМОТРЕЛ]->(рекомендация)
// 4. Возвращаем рекомендации
RETURN рекомендация.название, рекомендация.рейтинг,
COLLECT(DISTINCT любимый_жанр.название) as жанры
ORDER BY рекомендация.рейтинг DESC
LIMIT 10
"""
with self.driver.session() as session:
result = session.run(query, name=user_name)
recommendations = []
for record in result:
recommendations.append({
"фильм": record["рекомендация.название"],
"рейтинг": record["рекомендация.рейтинг"],
"жанры": record["жанры"]
})
return recommendations
def social_recommendations(self, user_name):
"""Рекомендации от друзей"""
query = """
// 1. Находим пользователя
MATCH (я:Пользователь {имя: $name})
// 2. Находим друзей
MATCH (я)-[:ДРУЖИТ_С]->(друг:Пользователь)
// 3. Что смотрели друзья (и им понравилось)
MATCH (друг)-[просмотр:СМОТРЕЛ]->(фильм)
WHERE просмотр.оценка >= 8 // Только то, что понравилось
// 4. Исключаем то, что я уже смотрел
WHERE NOT (я)-[:СМОТРЕЛ]->(фильм)
// 5. Считаем, сколько друзей рекомендует
RETURN фильм.название, фильм.рейтинг,
COUNT(DISTINCT друг) as друзей_рекомендуют,
COLLECT(DISTINCT друг.имя) as кто_рекомендует
ORDER BY друзей_рекомендуют DESC, фильм.рейтинг DESC
LIMIT 10
"""
with self.driver.session() as session:
result = session.run(query, name=user_name)
recommendations = []
for record in result:
recommendations.append({
"фильм": record["фильм.название"],
"рейтинг": record["фильм.рейтинг"],
"друзей_рекомендуют": record["друзей_рекомендуют"],
"кто_рекомендует": record["кто_рекомендует"]
})
return recommendations
# Используем рекомендатель
recommender = MovieRecommender(URI, USER, PASSWORD)
print("🎯 Рекомендации для Ивана:")
for rec in recommender.recommend_for_user("Иван"):
print(f" - {rec['фильм']} (рейтинг: {rec['рейтинг']}, жанры: {', '.join(rec['жанры'])})")
print("\n👥 Социальные рекомендации для Ивана:")
for rec in recommender.social_recommendations("Иван"):
print(f" - {rec['фильм']} (рекомендуют {rec['друзей_рекомендуют']} друзей: {', '.join(rec['кто_рекомендует'])})")
recommender.close()
Что должно получиться:
✅ Работающий код на Python для работы с Neo4j
✅ Рекомендательная система на графе знаний
✅ Понимание, как строить сложные запросы Cypher
🗃️ Шаг 6: Управление версионностью и миграции
Цель шага: Научиться управлять изменениями в структуре графа.
Проблема: Как обновлять граф знаний, когда добавляются новые типы данных или связи?
Решение 1: Скрипты миграций
Создайте папку migrations/ и храните там файлы с изменениями:
001_initial_schema.cypher:
cypher
// Создание ограничений и индексов для производительности
CREATE CONSTRAINT IF NOT EXISTS FOR (ф:Фильм) REQUIRE ф.id IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (п:Пользователь) REQUIRE п.id IS UNIQUE;
CREATE CONSTRAINT IF NOT EXISTS FOR (ж:Жанр) REQUIRE ж.id IS UNIQUE;
// Создание индексов для быстрого поиска
CREATE INDEX IF NOT EXISTS FOR (ф:Фильм) ON (ф.название);
CREATE INDEX IF NOT EXISTS FOR (п:Пользователь) ON (п.имя);
002_add_actors.cypher:
cypher
// Добавляем узлы актёров
CREATE (матч:Актер {
id: 'actor_001',
имя: 'Мэттью Макконахи',
страна: 'США'
})
CREATE (энн:Актер {
id: 'actor_002',
имя: 'Энн Хэтэуэй',
страна: 'США'
})
// Связываем актёров с фильмами
MATCH (ф:Фильм {название: 'Интерстеллар'})
MATCH (а:Актер {имя: 'Мэттью Макконахи'})
CREATE (а)-[:ИГРАЛ_В {роль: 'Купер'}]->(ф)
MATCH (ф:Фильм {название: 'Интерстеллар'})
MATCH (а:Актер {имя: 'Энн Хэтэуэй'})
CREATE (а)-[:ИГРАЛ_В {роль: 'Амелия'}]->(ф)
Решение 2: Программа для применения миграций
python
# migrations_runner.py
import os
from neo4j import GraphDatabase
class MigrationRunner:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def run_migrations(self, migrations_dir="migrations"):
"""Применяет все миграции из папки"""
# Получаем список файлов миграций
migrations = sorted([f for f in os.listdir(migrations_dir) if f.endswith('.cypher')])
print(f"🔍 Найдено {len(migrations)} миграций")
for migration_file in migrations:
print(f"🚀 Применяю {migration_file}...")
with open(os.path.join(migrations_dir, migration_file), 'r', encoding='utf-8') as f:
migration_query = f.read()
try:
with self.driver.session() as session:
result = session.run(migration_query)
# Принудительно выполняем запрос
list(result)
print(f"✅ {migration_file} успешно применена")
except Exception as e:
print(f"❌ Ошибка в {migration_file}: {e}")
break
def close(self):
self.driver.close()
# Применяем миграции
runner = MigrationRunner(URI, USER, PASSWORD)
runner.run_migrations()
runner.close()
Решение 3: Версионирование данных
cypher
// Хранение истории изменений узлов
// Создаём узел с версией
CREATE (ф1:Фильм:Версия {
id: 'interstellar_v1',
название: 'Интерстеллар',
рейтинг: 8.6,
версия: 1,
действует_с: '2014-01-01',
действует_до: '2020-12-31'
})
// Новая версия (рейтинг изменился)
CREATE (ф2:Фильм:Версия {
id: 'interstellar_v2',
название: 'Интерстеллар',
рейтинг: 8.7,
версия: 2,
действует_с: '2021-01-01',
действует_до: NULL // Текущая версия
})
// Связываем версии
CREATE (ф1)-[:СЛЕДУЮЩАЯ_ВЕРСИЯ]->(ф2)
// Запрос для получения актуальной версии
MATCH (ф:Фильм:Версия)
WHERE ф.действует_до IS NULL
RETURN ф.название, ф.рейтинг
Что должно получиться:
✅ Система миграций для графа знаний
✅ Возможность отката изменений
✅ Хранение истории данных
🔄 Шаг 7: Интеграция с ИИ-агентом — диалоговая система
Цель шага: Создать ИИ-агента, который отвечает на вопросы, используя граф знаний.
python
# knowledge_graph_agent.py
from neo4j import GraphDatabase
import re
class KnowledgeGraphAgent:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
# Шаблоны вопросов
self.patterns = {
'фильм_жанр': [
r'какой жанр у фильма (.+)',
r'жанр фильма (.+)',
r'к какому жанру относится (.+)'
],
'фильм_рейтинг': [
r'какой рейтинг у фильма (.+)',
r'оценка фильма (.+)',
r'сколько баллов у (.+)'
],
'рекомендация_жанр': [
r'посоветуй фильм в жанре (.+)',
r'что посмотреть из (.+)',
r'фильмы в жанре (.+)'
],
'что_смотрел': [
r'что смотрел (.+)',
r'какие фильмы посмотрел (.+)'
]
}
def close(self):
self.driver.close()
def extract_movie_name(self, question, pattern_group):
"""Извлекает название фильма из вопроса"""
for pattern in self.patterns.get(pattern_group, []):
match = re.search(pattern, question.lower())
if match:
# Убираем кавычки и лишние пробелы
movie_name = match.group(1).strip().strip('"').strip("'")
return movie_name
return None
def answer_question(self, question):
"""Основной метод для ответа на вопрос"""
question_lower = question.lower()
# 1. Вопрос о жанре фильма
if any(pattern in question_lower for pattern in ['жанр', 'относится']):
movie_name = self.extract_movie_name(question, 'фильм_жанр')
if movie_name:
return self.get_movie_genres(movie_name)
# 2. Вопрос о рейтинге
elif any(pattern in question_lower for pattern in ['рейтинг', 'оценка', 'балл']):
movie_name = self.extract_movie_name(question, 'фильм_рейтинг')
if movie_name:
return self.get_movie_rating(movie_name)
# 3. Рекомендация по жанру
elif any(pattern in question_lower for pattern in ['посоветуй', 'что посмотреть', 'фильмы в жанре']):
# Извлекаем жанр
for pattern in self.patterns['рекомендация_жанр']:
match = re.search(pattern, question_lower)
if match:
genre = match.group(1).strip()
return self.recommend_by_genre(genre)
# 4. Что смотрел пользователь
elif 'что смотрел' in question_lower or 'посмотрел' in question_lower:
# Извлекаем имя пользователя
for pattern in self.patterns['что_смотрел']:
match = re.search(pattern, question_lower)
if match:
user_name = match.group(1).strip()
return self.get_user_watched(user_name)
return "Извините, я не понял вопрос. Попробуйте спросить о фильмах, жанрах или рекомендациях."
def get_movie_genres(self, movie_name):
"""Получить жанры фильма"""
query = """
MATCH (ф:Фильм {название: $movie})-[:В_ЖАНРЕ]->(ж:Жанр)
RETURN ж.название as жанр
"""
with self.driver.session() as session:
result = session.run(query, movie=movie_name)
genres = [record["жанр"] for record in result]
if genres:
return f"Фильм '{movie_name}' относится к жанрам: {', '.join(genres)}"
else:
return f"Не могу найти информацию о фильме '{movie_name}'"
def get_movie_rating(self, movie_name):
"""Получить рейтинг фильма"""
query = """
MATCH (ф:Фильм {название: $movie})
RETURN ф.рейтинг as рейтинг
"""
with self.driver.session() as session:
result = session.run(query, movie=movie_name)
rating = [record["рейтинг"] for record in result]
if rating:
return f"Рейтинг фильма '{movie_name}': {rating[0]}/10"
else:
return f"Не могу найти рейтинг фильма '{movie_name}'"
def recommend_by_genre(self, genre_name):
"""Рекомендовать фильмы по жанру"""
query = """
MATCH (ф:Фильм)-[:В_ЖАНРЕ]->(ж:Жанр {название: $genre})
RETURN ф.название as фильм, ф.рейтинг as рейтинг
ORDER BY ф.рейтинг DESC
LIMIT 5
"""
with self.driver.session() as session:
result = session.run(query, genre=genre_name)
recommendations = [{"фильм": record["фильм"], "рейтинг": record["рейтинг"]} for record in result]
if recommendations:
response = f"📽️ Рекомендую в жанре '{genre_name}':\n"
for rec in recommendations:
response += f" • {rec['фильм']} (рейтинг: {rec['рейтинг']}/10)\n"
return response
else:
return f"К сожалению, в жанре '{genre_name}' нет фильмов в базе"
def get_user_watched(self, user_name):
"""Что смотрел пользователь"""
query = """
MATCH (п:Пользователь {имя: $user})-[:СМОТРЕЛ]->(ф:Фильм)
RETURN ф.название as фильм
"""
with self.driver.session() as session:
result = session.run(query, user=user_name)
films = [record["фильм"] for record in result]
if films:
return f"👤 {user_name} смотрел(а): {', '.join(films)}"
else:
return f"Не могу найти информацию о просмотрах пользователя {user_name}"
# Тестируем агента
agent = KnowledgeGraphAgent(URI, USER, PASSWORD)
questions = [
"Какой жанр у фильма Интерстеллар?",
"Какой рейтинг у Матрицы?",
"Посоветуй фильм в жанре Фантастика",
"Что смотрел Иван?",
"Что посмотреть из Драмы?"
]
print("🤖 ИИ-агент с графом знаний")
print("=" * 50)
for question in questions:
print(f"\n❓ Вопрос: {question}")
print(f"🤔 Ответ: {agent.answer_question(question)}")
agent.close()
Что должно получиться:
✅ Работающий ИИ-агент, использующий граф знаний
✅ Понимание естественных вопросов
✅ Полезные ответы на основе данных в графе
📊 Шаг 8: Визуализация и мониторинг
Цель шага: Научиться визуализировать граф знаний и отслеживать его состояние.
Веб-интерфейс Neo4j Browser:
Neo4j уже имеет отличный веб-интерфейс. Но можно создавать и свои дашборды:
python
# graph_dashboard.py
from neo4j import GraphDatabase
import json
class GraphDashboard:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def get_statistics(self):
"""Получить статистику графа"""
queries = {
"total_nodes": "MATCH (n) RETURN COUNT(n) as count",
"total_relationships": "MATCH ()-[r]->() RETURN COUNT(r) as count",
"nodes_by_type": """
MATCH (n)
RETURN labels(n)[0] as type, COUNT(*) as count
ORDER BY count DESC
""",
"relationships_by_type": """
MATCH ()-[r]->()
RETURN type(r) as type, COUNT(*) as count
ORDER BY count DESC
""",
"most_connected_nodes": """
MATCH (n)
RETURN n.имя as name, labels(n)[0] as type,
size((n)--()) as connections
ORDER BY connections DESC
LIMIT 10
"""
}
stats = {}
with self.driver.session() as session:
for name, query in queries.items():
result = session.run(query)
stats[name] = [dict(record) for record in result]
return stats
def export_graph(self, format='json'):
"""Экспортировать граф для визуализации"""
if format == 'json':
query = """
MATCH (n)
OPTIONAL MATCH (n)-[r]->(m)
RETURN n, r, m
LIMIT 100 // Ограничиваем для производительности
"""
with self.driver.session() as session:
result = session.run(query)
nodes = {}
edges = []
for record in result:
# Обрабатываем узлы
node_n = record['n']
if node_n:
node_id = str(node_n.id)
if node_id not in nodes:
nodes[node_id] = {
'id': node_id,
'labels': list(node_n.labels),
'properties': dict(node_n)
}
# Обрабатываем связи
rel = record['r']
if rel:
edge_id = str(rel.id)
start_node = str(rel.start_node.id)
end_node = str(rel.end_node.id)
edges.append({
'id': edge_id,
'type': rel.type,
'start': start_node,
'end': end_node,
'properties': dict(rel)
})
graph_data = {
'nodes': list(nodes.values()),
'edges': edges
}
return json.dumps(graph_data, ensure_ascii=False, indent=2)
def close(self):
self.driver.close()
# Используем дашборд
dashboard = GraphDashboard(URI, USER, PASSWORD)
print("📊 Статистика графа знаний:")
stats = dashboard.get_statistics()
print(f"\n📈 Всего узлов: {stats['total_nodes'][0]['count']}")
print(f"🔗 Всего связей: {stats['total_relationships'][0]['count']}")
print("\n🏷️ Узлы по типам:")
for item in stats['nodes_by_type']:
print(f" - {item['type']}: {item['count']}")
print("\n🔗 Связи по типам:")
for item in stats['relationships_by_type']:
print(f" - {item['type']}: {item['count']}")
print("\n🌟 Самые связанные узлы:")
for item in stats['most_connected_nodes']:
print(f" - {item['name']} ({item['type']}): {item['connections']} связей")
# Экспортируем для визуализации
graph_json = dashboard.export_graph()
with open('graph_export.json', 'w', encoding='utf-8') as f:
f.write(graph_json)
print("\n💾 Граф экспортирован в graph_export.json")
dashboard.close()
Визуализация с помощью D3.js (веб-интерфейс):
Создайте файл visualization.html:
html
<!DOCTYPE html>
<html>
<head>
<title>Визуализация графа знаний</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
.node {
cursor: pointer;
stroke: #fff;
stroke-width: 1.5px;
}
.link {
fill: none;
stroke: #999;
stroke-opacity: 0.6;
}
.node text {
font: 12px sans-serif;
pointer-events: none;
}
</style>
</head>
<body>
<h1>Граф знаний фильмов</h1>
<div id="graph"></div>
<script>
// Загружаем данные графа
d3.json("graph_export.json").then(function(data) {
const width = 1200;
const height = 800;
const svg = d3.select("#graph")
.append("svg")
.attr("width", width)
.attr("height", height);
// Создаём симуляцию
const simulation = d3.forceSimulation(data.nodes)
.force("link", d3.forceLink(data.edges).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-300))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(30));
// Добавляем связи
const link = svg.append("g")
.selectAll("line")
.data(data.edges)
.enter()
.append("line")
.attr("class", "link")
.attr("stroke-width", 2);
// Добавляем узлы
const node = svg.append("g")
.selectAll("circle")
.data(data.nodes)
.enter()
.append("circle")
.attr("class", "node")
.attr("r", 10)
.attr("fill", d => {
// Разные цвета для разных типов узлов
if (d.labels.includes('Фильм')) return '#ff6b6b';
if (d.labels.includes('Пользователь')) return '#4ecdc4';
if (d.labels.includes('Жанр')) return '#45b7d1';
if (d.labels.includes('Актер')) return '#96ceb4';
return '#ffeaa7';
})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Добавляем текст
const text = svg.append("g")
.selectAll("text")
.data(data.nodes)
.enter()
.append("text")
.text(d => d.properties.имя || d.properties.название || d.properties.id)
.attr("font-size", "12px")
.attr("dx", 15)
.attr("dy", 4);
// Обновление позиций
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
text
.attr("x", d => d.x)
.attr("y", d => d.y);
});
// Функции для перетаскивания
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
// Информация при клике
node.on("click", function(event, d) {
console.log("Узел:", d);
alert(`Узел: ${d.labels.join(', ')}\nСвойства: ${JSON.stringify(d.properties, null, 2)}`);
});
});
</script>
</body>
</html>
Что должно получиться:
✅ Статистика графа знаний
✅ Экспорт данных для визуализации
✅ Интерактивная визуализация в браузере
🎓 Итог: Что вы научились делать
✅ Вы освоили:
- Установку и настройку Neo4j — графовой базы данных
- Создание графов знаний на языке Cypher
- Запросы к графу для поиска информации
- Работу из Python с использованием драйвера Neo4j
- Создание ИИ-агента, использующего граф знаний
- Управление версионностью и миграциями
- Визуализацию графа для анализа
🗄️ Где хранить граф знаний:
- Локально (Neo4j Desktop или Docker) — для разработки
- Neo4j Aura (облачный сервис) — для продакшена
- Серверная установка — для полного контроля
🔧 Как управлять версионностью:
- Миграционные скрипты — для изменений структуры
- Docker образы — для воспроизводимости
- Резервные копии — регулярный экспорт данных
💻 На каком языке писать код:
- Cypher — для работы с графом (запросы)
- Python — для интеграции с ИИ-агентами
- JavaScript — для веб-визуализации
🚀 Следующие шаги для развития:
- Изучите продвинутые запросы Cypher — паттерны, агрегации, оконные функции
- Добавьте машинное обучение — рекомендательные системы на графах
- Интегрируйте NLP — для понимания естественного языка
- Настройте автоматическое обновление — из внешних источников данных
- Создайте распределённый граф — для больших объёмов данных
💡 Помните:
- Начинайте с малого — небольшая предметная область
- Тестируйте запросы в Neo4j Browser перед написанием кода
- Документируйте структуру графа — какие типы узлов и связей существуют
- Мониторьте производительность — создавайте индексы для часто искомых полей
Удачи в создании ваших умных ИИ-агентов с графами знаний! 🧠🚀