graphiti-core 0.18.0__tar.gz → 0.18.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of graphiti-core might be problematic. Click here for more details.
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/unit_tests.yml +27 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/PKG-INFO +11 -3
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/README.md +10 -2
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/driver/driver.py +20 -2
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/driver/falkordb_driver.py +16 -9
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/driver/neo4j_driver.py +8 -6
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/edges.py +73 -99
- graphiti_core-0.18.1/graphiti_core/graph_queries.py +103 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/graphiti.py +23 -8
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/helpers.py +3 -2
- graphiti_core-0.18.1/graphiti_core/models/edges/edge_db_queries.py +130 -0
- graphiti_core-0.18.1/graphiti_core/models/nodes/node_db_queries.py +133 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/nodes.py +113 -128
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/dedupe_nodes.py +1 -1
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/extract_nodes.py +12 -10
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_filters.py +5 -5
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_utils.py +139 -184
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/bulk_utils.py +3 -5
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/community_operations.py +11 -7
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/edge_operations.py +19 -50
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/graph_data_operations.py +14 -29
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/node_operations.py +11 -55
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/pyproject.toml +4 -4
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/signatures/version1/cla.json +16 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/driver/test_falkordb_driver.py +10 -15
- graphiti_core-0.18.1/tests/helpers_test.py +96 -0
- graphiti_core-0.18.1/tests/test_edge_int.py +384 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/test_entity_exclusion_int.py +36 -19
- graphiti_core-0.18.1/tests/test_graphiti_int.py +78 -0
- graphiti_core-0.18.1/tests/test_node_int.py +243 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/uv.lock +1 -1
- graphiti_core-0.18.0/graphiti_core/graph_queries.py +0 -149
- graphiti_core-0.18.0/graphiti_core/models/edges/edge_db_queries.py +0 -56
- graphiti_core-0.18.0/graphiti_core/models/nodes/node_db_queries.py +0 -52
- graphiti_core-0.18.0/tests/helpers_test.py +0 -38
- graphiti_core-0.18.0/tests/test_graphiti_falkordb_int.py +0 -164
- graphiti_core-0.18.0/tests/test_graphiti_int.py +0 -149
- graphiti_core-0.18.0/tests/test_node_falkordb_int.py +0 -139
- graphiti_core-0.18.0/tests/test_node_int.py +0 -122
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.env.example +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/dependabot.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/pull_request_template.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/secret_scanning.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/cla.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/claude-code-review.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/claude.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/codeql.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/lint.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/mcp-server-docker.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/release-graphiti-core.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/typecheck.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.gitignore +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/CLAUDE.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/CODE_OF_CONDUCT.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/CONTRIBUTING.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/Dockerfile +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/LICENSE +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/Makefile +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/SECURITY.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/Zep-CLA.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/conftest.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/depot.json +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/docker-compose.test.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/docker-compose.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/ellipsis.yaml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/data/manybirds_products.json +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/ecommerce/runner.ipynb +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/ecommerce/runner.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/langgraph-agent/agent.ipynb +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/langgraph-agent/tinybirds-jess.png +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/podcast/podcast_runner.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/podcast/podcast_transcript.txt +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/podcast/transcript_parser.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/quickstart/README.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/quickstart/quickstart_falkordb.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/quickstart/quickstart_neo4j.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/quickstart/requirements.txt +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/wizard_of_oz/parser.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/wizard_of_oz/runner.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/wizard_of_oz/woo.txt +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/bge_reranker_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/gemini_reranker_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/openai_reranker_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/driver/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/azure_openai.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/gemini.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/openai.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/voyage.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/errors.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/graphiti_types.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/anthropic_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/azure_openai_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/config.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/errors.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/gemini_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/groq_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/openai_base_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/openai_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/openai_generic_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/utils.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/models/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/models/edges/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/models/nodes/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/dedupe_edges.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/eval.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/extract_edge_dates.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/extract_edges.py +4 -4
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/invalidate_edges.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/lib.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/models.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/prompt_helpers.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/summarize_nodes.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/py.typed +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_config.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_config_recipes.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_helpers.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/telemetry/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/telemetry/telemetry.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/datetime_utils.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/temporal_operations.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/utils.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/ontology_utils/entity_types_utils.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/images/arxiv-screenshot.png +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/images/graphiti-graph-intro.gif +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/images/graphiti-intro-slides-stock-2.gif +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/images/simple_graph.svg +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/.env.example +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/.python-version +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/Dockerfile +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/README.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/cursor_rules.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/docker-compose.yml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/graphiti_mcp_server.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/mcp_config_sse_example.json +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/mcp_config_stdio_example.json +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/pyproject.toml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/uv.lock +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/poetry.lock +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/py.typed +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/pytest.ini +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/.env.example +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/Makefile +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/README.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/config.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/dto/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/dto/common.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/dto/ingest.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/dto/retrieve.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/main.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/routers/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/routers/ingest.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/routers/retrieve.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/zep_graphiti.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/pyproject.toml +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/uv.lock +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/cross_encoder/test_bge_reranker_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/cross_encoder/test_gemini_reranker_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/driver/__init__.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/embedder/embedder_fixtures.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/embedder/test_gemini.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/embedder/test_openai.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/embedder/test_voyage.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/data/longmemeval_data/README.md +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/data/longmemeval_data/longmemeval_oracle.json +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/eval_cli.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/eval_e2e_graph_building.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/pytest.ini +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/utils.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_anthropic_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_anthropic_client_int.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_errors.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_gemini_client.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/utils/maintenance/test_edge_operations.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/utils/maintenance/test_temporal_operations_int.py +0 -0
- {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/utils/search/search_utils_test.py +0 -0
|
@@ -20,6 +20,15 @@ jobs:
|
|
|
20
20
|
ports:
|
|
21
21
|
- 6379:6379
|
|
22
22
|
options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
|
|
23
|
+
neo4j:
|
|
24
|
+
image: neo4j:5.26-community
|
|
25
|
+
ports:
|
|
26
|
+
- 7687:7687
|
|
27
|
+
- 7474:7474
|
|
28
|
+
env:
|
|
29
|
+
NEO4J_AUTH: neo4j/testpass
|
|
30
|
+
NEO4J_PLUGINS: '["apoc"]'
|
|
31
|
+
options: --health-cmd "cypher-shell -u neo4j -p testpass 'RETURN 1'" --health-interval 10s --health-timeout 5s --health-retries 10
|
|
23
32
|
steps:
|
|
24
33
|
- uses: actions/checkout@v4
|
|
25
34
|
- name: Set up Python
|
|
@@ -37,15 +46,33 @@ jobs:
|
|
|
37
46
|
- name: Run non-integration tests
|
|
38
47
|
env:
|
|
39
48
|
PYTHONPATH: ${{ github.workspace }}
|
|
49
|
+
NEO4J_URI: bolt://localhost:7687
|
|
50
|
+
NEO4J_USER: neo4j
|
|
51
|
+
NEO4J_PASSWORD: testpass
|
|
40
52
|
run: |
|
|
41
53
|
uv run pytest -m "not integration"
|
|
42
54
|
- name: Wait for FalkorDB
|
|
43
55
|
run: |
|
|
44
56
|
timeout 60 bash -c 'until redis-cli -h localhost -p 6379 ping; do sleep 1; done'
|
|
57
|
+
- name: Wait for Neo4j
|
|
58
|
+
run: |
|
|
59
|
+
timeout 60 bash -c 'until wget -O /dev/null http://localhost:7474 >/dev/null 2>&1; do sleep 1; done'
|
|
45
60
|
- name: Run FalkorDB integration tests
|
|
46
61
|
env:
|
|
47
62
|
PYTHONPATH: ${{ github.workspace }}
|
|
48
63
|
FALKORDB_HOST: localhost
|
|
49
64
|
FALKORDB_PORT: 6379
|
|
65
|
+
DISABLE_NEO4J: 1
|
|
50
66
|
run: |
|
|
51
67
|
uv run pytest tests/driver/test_falkordb_driver.py
|
|
68
|
+
- name: Run Neo4j integration tests
|
|
69
|
+
env:
|
|
70
|
+
PYTHONPATH: ${{ github.workspace }}
|
|
71
|
+
NEO4J_URI: bolt://localhost:7687
|
|
72
|
+
NEO4J_USER: neo4j
|
|
73
|
+
NEO4J_PASSWORD: testpass
|
|
74
|
+
FALKORDB_HOST: localhost
|
|
75
|
+
FALKORDB_PORT: 6379
|
|
76
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
77
|
+
run: |
|
|
78
|
+
uv run pytest tests/test_*_int.py -k "neo4j"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphiti-core
|
|
3
|
-
Version: 0.18.
|
|
3
|
+
Version: 0.18.1
|
|
4
4
|
Summary: A temporal graph building library
|
|
5
5
|
Project-URL: Homepage, https://help.getzep.com/graphiti/graphiti/overview
|
|
6
6
|
Project-URL: Repository, https://github.com/getzep/graphiti
|
|
@@ -106,9 +106,9 @@ nodes ("Kendra", "Adidas shoes"), and their relationship, or edge ("loves"). Kno
|
|
|
106
106
|
extensively for information retrieval. What makes Graphiti unique is its ability to autonomously build a knowledge graph
|
|
107
107
|
while handling changing relationships and maintaining historical context.
|
|
108
108
|
|
|
109
|
-
## Graphiti and Zep
|
|
109
|
+
## Graphiti and Zep's Context Engineering Platform.
|
|
110
110
|
|
|
111
|
-
Graphiti powers the core of [Zep
|
|
111
|
+
Graphiti powers the core of [Zep](https://www.getzep.com), a turn-key context engineering platform for AI Agents. Zep offers agent memory, Graph RAG for dynamic data, and context retrieval and assembly.
|
|
112
112
|
|
|
113
113
|
Using Graphiti, we've demonstrated Zep is
|
|
114
114
|
the [State of the Art in Agent Memory](https://blog.getzep.com/state-of-the-art-agent-memory/).
|
|
@@ -219,6 +219,14 @@ pip install graphiti-core[anthropic,groq,google-genai]
|
|
|
219
219
|
pip install graphiti-core[falkordb,anthropic,google-genai]
|
|
220
220
|
```
|
|
221
221
|
|
|
222
|
+
## Default to Low Concurrency; LLM Provider 429 Rate Limit Errors
|
|
223
|
+
|
|
224
|
+
Graphiti's ingestion pipelines are designed for high concurrency. By default, concurrency is set low to avoid LLM Provider 429 Rate Limit Errors. If you find Graphiti slow, please increase concurrency as described below.
|
|
225
|
+
|
|
226
|
+
Concurrency controlled by the `SEMAPHORE_LIMIT` environment variable. By default, `SEMAPHORE_LIMIT` is set to `10` concurrent operations to help prevent `429` rate limit errors from your LLM provider. If you encounter such errors, try lowering this value.
|
|
227
|
+
|
|
228
|
+
If your LLM provider allows higher throughput, you can increase `SEMAPHORE_LIMIT` to boost episode ingestion performance.
|
|
229
|
+
|
|
222
230
|
## Quick Start
|
|
223
231
|
|
|
224
232
|
> [!IMPORTANT]
|
|
@@ -54,9 +54,9 @@ nodes ("Kendra", "Adidas shoes"), and their relationship, or edge ("loves"). Kno
|
|
|
54
54
|
extensively for information retrieval. What makes Graphiti unique is its ability to autonomously build a knowledge graph
|
|
55
55
|
while handling changing relationships and maintaining historical context.
|
|
56
56
|
|
|
57
|
-
## Graphiti and Zep
|
|
57
|
+
## Graphiti and Zep's Context Engineering Platform.
|
|
58
58
|
|
|
59
|
-
Graphiti powers the core of [Zep
|
|
59
|
+
Graphiti powers the core of [Zep](https://www.getzep.com), a turn-key context engineering platform for AI Agents. Zep offers agent memory, Graph RAG for dynamic data, and context retrieval and assembly.
|
|
60
60
|
|
|
61
61
|
Using Graphiti, we've demonstrated Zep is
|
|
62
62
|
the [State of the Art in Agent Memory](https://blog.getzep.com/state-of-the-art-agent-memory/).
|
|
@@ -167,6 +167,14 @@ pip install graphiti-core[anthropic,groq,google-genai]
|
|
|
167
167
|
pip install graphiti-core[falkordb,anthropic,google-genai]
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
+
## Default to Low Concurrency; LLM Provider 429 Rate Limit Errors
|
|
171
|
+
|
|
172
|
+
Graphiti's ingestion pipelines are designed for high concurrency. By default, concurrency is set low to avoid LLM Provider 429 Rate Limit Errors. If you find Graphiti slow, please increase concurrency as described below.
|
|
173
|
+
|
|
174
|
+
Concurrency controlled by the `SEMAPHORE_LIMIT` environment variable. By default, `SEMAPHORE_LIMIT` is set to `10` concurrent operations to help prevent `429` rate limit errors from your LLM provider. If you encounter such errors, try lowering this value.
|
|
175
|
+
|
|
176
|
+
If your LLM provider allows higher throughput, you can increase `SEMAPHORE_LIMIT` to boost episode ingestion performance.
|
|
177
|
+
|
|
170
178
|
## Quick Start
|
|
171
179
|
|
|
172
180
|
> [!IMPORTANT]
|
|
@@ -14,14 +14,21 @@ See the License for the specific language governing permissions and
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
import copy
|
|
17
18
|
import logging
|
|
18
19
|
from abc import ABC, abstractmethod
|
|
19
20
|
from collections.abc import Coroutine
|
|
21
|
+
from enum import Enum
|
|
20
22
|
from typing import Any
|
|
21
23
|
|
|
22
24
|
logger = logging.getLogger(__name__)
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
class GraphProvider(Enum):
|
|
28
|
+
NEO4J = 'neo4j'
|
|
29
|
+
FALKORDB = 'falkordb'
|
|
30
|
+
|
|
31
|
+
|
|
25
32
|
class GraphDriverSession(ABC):
|
|
26
33
|
async def __aenter__(self):
|
|
27
34
|
return self
|
|
@@ -45,10 +52,11 @@ class GraphDriverSession(ABC):
|
|
|
45
52
|
|
|
46
53
|
|
|
47
54
|
class GraphDriver(ABC):
|
|
48
|
-
provider:
|
|
55
|
+
provider: GraphProvider
|
|
49
56
|
fulltext_syntax: str = (
|
|
50
57
|
'' # Neo4j (default) syntax does not require a prefix for fulltext queries
|
|
51
58
|
)
|
|
59
|
+
_database: str
|
|
52
60
|
|
|
53
61
|
@abstractmethod
|
|
54
62
|
def execute_query(self, cypher_query_: str, **kwargs: Any) -> Coroutine:
|
|
@@ -63,5 +71,15 @@ class GraphDriver(ABC):
|
|
|
63
71
|
raise NotImplementedError()
|
|
64
72
|
|
|
65
73
|
@abstractmethod
|
|
66
|
-
def delete_all_indexes(self
|
|
74
|
+
def delete_all_indexes(self) -> Coroutine:
|
|
67
75
|
raise NotImplementedError()
|
|
76
|
+
|
|
77
|
+
def with_database(self, database: str) -> 'GraphDriver':
|
|
78
|
+
"""
|
|
79
|
+
Returns a shallow copy of this driver with a different default database.
|
|
80
|
+
Reuses the same connection (e.g. FalkorDB, Neo4j).
|
|
81
|
+
"""
|
|
82
|
+
cloned = copy.copy(self)
|
|
83
|
+
cloned._database = database
|
|
84
|
+
|
|
85
|
+
return cloned
|
|
@@ -32,7 +32,7 @@ else:
|
|
|
32
32
|
'Install it with: pip install graphiti-core[falkordb]'
|
|
33
33
|
) from None
|
|
34
34
|
|
|
35
|
-
from graphiti_core.driver.driver import GraphDriver, GraphDriverSession
|
|
35
|
+
from graphiti_core.driver.driver import GraphDriver, GraphDriverSession, GraphProvider
|
|
36
36
|
|
|
37
37
|
logger = logging.getLogger(__name__)
|
|
38
38
|
|
|
@@ -71,7 +71,7 @@ class FalkorDriverSession(GraphDriverSession):
|
|
|
71
71
|
|
|
72
72
|
|
|
73
73
|
class FalkorDriver(GraphDriver):
|
|
74
|
-
provider
|
|
74
|
+
provider = GraphProvider.FALKORDB
|
|
75
75
|
|
|
76
76
|
def __init__(
|
|
77
77
|
self,
|
|
@@ -90,12 +90,13 @@ class FalkorDriver(GraphDriver):
|
|
|
90
90
|
The default parameters assume a local (on-premises) FalkorDB instance.
|
|
91
91
|
"""
|
|
92
92
|
super().__init__()
|
|
93
|
+
|
|
94
|
+
self._database = database
|
|
93
95
|
if falkor_db is not None:
|
|
94
96
|
# If a FalkorDB instance is provided, use it directly
|
|
95
97
|
self.client = falkor_db
|
|
96
98
|
else:
|
|
97
99
|
self.client = FalkorDB(host=host, port=port, username=username, password=password)
|
|
98
|
-
self._database = database
|
|
99
100
|
|
|
100
101
|
self.fulltext_syntax = '@' # FalkorDB uses a redisearch-like syntax for fulltext queries see https://redis.io/docs/latest/develop/ai/search-and-query/query/full-text/
|
|
101
102
|
|
|
@@ -106,8 +107,7 @@ class FalkorDriver(GraphDriver):
|
|
|
106
107
|
return self.client.select_graph(graph_name)
|
|
107
108
|
|
|
108
109
|
async def execute_query(self, cypher_query_, **kwargs: Any):
|
|
109
|
-
|
|
110
|
-
graph = self._get_graph(graph_name)
|
|
110
|
+
graph = self._get_graph(self._database)
|
|
111
111
|
|
|
112
112
|
# Convert datetime objects to ISO strings (FalkorDB does not support datetime objects directly)
|
|
113
113
|
params = convert_datetimes_to_strings(dict(kwargs))
|
|
@@ -119,7 +119,7 @@ class FalkorDriver(GraphDriver):
|
|
|
119
119
|
# check if index already exists
|
|
120
120
|
logger.info(f'Index already exists: {e}')
|
|
121
121
|
return None
|
|
122
|
-
logger.error(f'Error executing FalkorDB query: {e}')
|
|
122
|
+
logger.error(f'Error executing FalkorDB query: {e}\n{cypher_query_}\n{params}')
|
|
123
123
|
raise
|
|
124
124
|
|
|
125
125
|
# Convert the result header to a list of strings
|
|
@@ -151,13 +151,20 @@ class FalkorDriver(GraphDriver):
|
|
|
151
151
|
elif hasattr(self.client.connection, 'close'):
|
|
152
152
|
await self.client.connection.close()
|
|
153
153
|
|
|
154
|
-
async def delete_all_indexes(self
|
|
155
|
-
database = database_ or self._database
|
|
154
|
+
async def delete_all_indexes(self) -> None:
|
|
156
155
|
await self.execute_query(
|
|
157
156
|
'CALL db.indexes() YIELD name DROP INDEX name',
|
|
158
|
-
database_=database,
|
|
159
157
|
)
|
|
160
158
|
|
|
159
|
+
def clone(self, database: str) -> 'GraphDriver':
|
|
160
|
+
"""
|
|
161
|
+
Returns a shallow copy of this driver with a different default database.
|
|
162
|
+
Reuses the same connection (e.g. FalkorDB, Neo4j).
|
|
163
|
+
"""
|
|
164
|
+
cloned = FalkorDriver(falkor_db=self.client, database=database)
|
|
165
|
+
|
|
166
|
+
return cloned
|
|
167
|
+
|
|
161
168
|
|
|
162
169
|
def convert_datetimes_to_strings(obj):
|
|
163
170
|
if isinstance(obj, dict):
|
|
@@ -21,13 +21,13 @@ from typing import Any
|
|
|
21
21
|
from neo4j import AsyncGraphDatabase, EagerResult
|
|
22
22
|
from typing_extensions import LiteralString
|
|
23
23
|
|
|
24
|
-
from graphiti_core.driver.driver import GraphDriver, GraphDriverSession
|
|
24
|
+
from graphiti_core.driver.driver import GraphDriver, GraphDriverSession, GraphProvider
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger(__name__)
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class Neo4jDriver(GraphDriver):
|
|
30
|
-
provider
|
|
30
|
+
provider = GraphProvider.NEO4J
|
|
31
31
|
|
|
32
32
|
def __init__(self, uri: str, user: str | None, password: str | None, database: str = 'neo4j'):
|
|
33
33
|
super().__init__()
|
|
@@ -45,7 +45,11 @@ class Neo4jDriver(GraphDriver):
|
|
|
45
45
|
params = {}
|
|
46
46
|
params.setdefault('database_', self._database)
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
try:
|
|
49
|
+
result = await self.client.execute_query(cypher_query_, parameters_=params, **kwargs)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
logger.error(f'Error executing Neo4j query: {e}\n{cypher_query_}\n{params}')
|
|
52
|
+
raise
|
|
49
53
|
|
|
50
54
|
return result
|
|
51
55
|
|
|
@@ -56,9 +60,7 @@ class Neo4jDriver(GraphDriver):
|
|
|
56
60
|
async def close(self) -> None:
|
|
57
61
|
return await self.client.close()
|
|
58
62
|
|
|
59
|
-
def delete_all_indexes(self
|
|
60
|
-
database = database_ or self._database
|
|
63
|
+
def delete_all_indexes(self) -> Coroutine[Any, Any, EagerResult]:
|
|
61
64
|
return self.client.execute_query(
|
|
62
65
|
'CALL db.indexes() YIELD name DROP INDEX name',
|
|
63
|
-
database_=database,
|
|
64
66
|
)
|
|
@@ -29,29 +29,17 @@ from graphiti_core.embedder import EmbedderClient
|
|
|
29
29
|
from graphiti_core.errors import EdgeNotFoundError, GroupsEdgesNotFoundError
|
|
30
30
|
from graphiti_core.helpers import parse_db_date
|
|
31
31
|
from graphiti_core.models.edges.edge_db_queries import (
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
COMMUNITY_EDGE_RETURN,
|
|
33
|
+
ENTITY_EDGE_RETURN,
|
|
34
|
+
EPISODIC_EDGE_RETURN,
|
|
34
35
|
EPISODIC_EDGE_SAVE,
|
|
36
|
+
get_community_edge_save_query,
|
|
37
|
+
get_entity_edge_save_query,
|
|
35
38
|
)
|
|
36
39
|
from graphiti_core.nodes import Node
|
|
37
40
|
|
|
38
41
|
logger = logging.getLogger(__name__)
|
|
39
42
|
|
|
40
|
-
ENTITY_EDGE_RETURN: LiteralString = """
|
|
41
|
-
RETURN
|
|
42
|
-
e.uuid AS uuid,
|
|
43
|
-
startNode(e).uuid AS source_node_uuid,
|
|
44
|
-
endNode(e).uuid AS target_node_uuid,
|
|
45
|
-
e.created_at AS created_at,
|
|
46
|
-
e.name AS name,
|
|
47
|
-
e.group_id AS group_id,
|
|
48
|
-
e.fact AS fact,
|
|
49
|
-
e.episodes AS episodes,
|
|
50
|
-
e.expired_at AS expired_at,
|
|
51
|
-
e.valid_at AS valid_at,
|
|
52
|
-
e.invalid_at AS invalid_at,
|
|
53
|
-
properties(e) AS attributes"""
|
|
54
|
-
|
|
55
43
|
|
|
56
44
|
class Edge(BaseModel, ABC):
|
|
57
45
|
uuid: str = Field(default_factory=lambda: str(uuid4()))
|
|
@@ -66,9 +54,9 @@ class Edge(BaseModel, ABC):
|
|
|
66
54
|
async def delete(self, driver: GraphDriver):
|
|
67
55
|
result = await driver.execute_query(
|
|
68
56
|
"""
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
57
|
+
MATCH (n)-[e:MENTIONS|RELATES_TO|HAS_MEMBER {uuid: $uuid}]->(m)
|
|
58
|
+
DELETE e
|
|
59
|
+
""",
|
|
72
60
|
uuid=self.uuid,
|
|
73
61
|
)
|
|
74
62
|
|
|
@@ -107,14 +95,10 @@ class EpisodicEdge(Edge):
|
|
|
107
95
|
async def get_by_uuid(cls, driver: GraphDriver, uuid: str):
|
|
108
96
|
records, _, _ = await driver.execute_query(
|
|
109
97
|
"""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
n.uuid AS source_node_uuid,
|
|
115
|
-
m.uuid AS target_node_uuid,
|
|
116
|
-
e.created_at AS created_at
|
|
117
|
-
""",
|
|
98
|
+
MATCH (n:Episodic)-[e:MENTIONS {uuid: $uuid}]->(m:Entity)
|
|
99
|
+
RETURN
|
|
100
|
+
"""
|
|
101
|
+
+ EPISODIC_EDGE_RETURN,
|
|
118
102
|
uuid=uuid,
|
|
119
103
|
routing_='r',
|
|
120
104
|
)
|
|
@@ -129,15 +113,11 @@ class EpisodicEdge(Edge):
|
|
|
129
113
|
async def get_by_uuids(cls, driver: GraphDriver, uuids: list[str]):
|
|
130
114
|
records, _, _ = await driver.execute_query(
|
|
131
115
|
"""
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
n.uuid AS source_node_uuid,
|
|
138
|
-
m.uuid AS target_node_uuid,
|
|
139
|
-
e.created_at AS created_at
|
|
140
|
-
""",
|
|
116
|
+
MATCH (n:Episodic)-[e:MENTIONS]->(m:Entity)
|
|
117
|
+
WHERE e.uuid IN $uuids
|
|
118
|
+
RETURN
|
|
119
|
+
"""
|
|
120
|
+
+ EPISODIC_EDGE_RETURN,
|
|
141
121
|
uuids=uuids,
|
|
142
122
|
routing_='r',
|
|
143
123
|
)
|
|
@@ -161,19 +141,17 @@ class EpisodicEdge(Edge):
|
|
|
161
141
|
|
|
162
142
|
records, _, _ = await driver.execute_query(
|
|
163
143
|
"""
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
144
|
+
MATCH (n:Episodic)-[e:MENTIONS]->(m:Entity)
|
|
145
|
+
WHERE e.group_id IN $group_ids
|
|
146
|
+
"""
|
|
167
147
|
+ cursor_query
|
|
168
148
|
+ """
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
ORDER BY e.uuid DESC
|
|
176
|
-
"""
|
|
149
|
+
RETURN
|
|
150
|
+
"""
|
|
151
|
+
+ EPISODIC_EDGE_RETURN
|
|
152
|
+
+ """
|
|
153
|
+
ORDER BY e.uuid DESC
|
|
154
|
+
"""
|
|
177
155
|
+ limit_query,
|
|
178
156
|
group_ids=group_ids,
|
|
179
157
|
uuid=uuid_cursor,
|
|
@@ -221,11 +199,14 @@ class EntityEdge(Edge):
|
|
|
221
199
|
return self.fact_embedding
|
|
222
200
|
|
|
223
201
|
async def load_fact_embedding(self, driver: GraphDriver):
|
|
224
|
-
|
|
202
|
+
records, _, _ = await driver.execute_query(
|
|
203
|
+
"""
|
|
225
204
|
MATCH (n:Entity)-[e:RELATES_TO {uuid: $uuid}]->(m:Entity)
|
|
226
205
|
RETURN e.fact_embedding AS fact_embedding
|
|
227
|
-
|
|
228
|
-
|
|
206
|
+
""",
|
|
207
|
+
uuid=self.uuid,
|
|
208
|
+
routing_='r',
|
|
209
|
+
)
|
|
229
210
|
|
|
230
211
|
if len(records) == 0:
|
|
231
212
|
raise EdgeNotFoundError(self.uuid)
|
|
@@ -251,7 +232,7 @@ class EntityEdge(Edge):
|
|
|
251
232
|
edge_data.update(self.attributes or {})
|
|
252
233
|
|
|
253
234
|
result = await driver.execute_query(
|
|
254
|
-
|
|
235
|
+
get_entity_edge_save_query(driver.provider),
|
|
255
236
|
edge_data=edge_data,
|
|
256
237
|
)
|
|
257
238
|
|
|
@@ -263,8 +244,9 @@ class EntityEdge(Edge):
|
|
|
263
244
|
async def get_by_uuid(cls, driver: GraphDriver, uuid: str):
|
|
264
245
|
records, _, _ = await driver.execute_query(
|
|
265
246
|
"""
|
|
266
|
-
|
|
267
|
-
|
|
247
|
+
MATCH (n:Entity)-[e:RELATES_TO {uuid: $uuid}]->(m:Entity)
|
|
248
|
+
RETURN
|
|
249
|
+
"""
|
|
268
250
|
+ ENTITY_EDGE_RETURN,
|
|
269
251
|
uuid=uuid,
|
|
270
252
|
routing_='r',
|
|
@@ -283,9 +265,10 @@ class EntityEdge(Edge):
|
|
|
283
265
|
|
|
284
266
|
records, _, _ = await driver.execute_query(
|
|
285
267
|
"""
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
268
|
+
MATCH (n:Entity)-[e:RELATES_TO]->(m:Entity)
|
|
269
|
+
WHERE e.uuid IN $uuids
|
|
270
|
+
RETURN
|
|
271
|
+
"""
|
|
289
272
|
+ ENTITY_EDGE_RETURN,
|
|
290
273
|
uuids=uuids,
|
|
291
274
|
routing_='r',
|
|
@@ -314,22 +297,21 @@ class EntityEdge(Edge):
|
|
|
314
297
|
else ''
|
|
315
298
|
)
|
|
316
299
|
|
|
317
|
-
|
|
300
|
+
records, _, _ = await driver.execute_query(
|
|
318
301
|
"""
|
|
319
302
|
MATCH (n:Entity)-[e:RELATES_TO]->(m:Entity)
|
|
320
303
|
WHERE e.group_id IN $group_ids
|
|
321
304
|
"""
|
|
322
305
|
+ cursor_query
|
|
306
|
+
+ """
|
|
307
|
+
RETURN
|
|
308
|
+
"""
|
|
323
309
|
+ ENTITY_EDGE_RETURN
|
|
324
310
|
+ with_embeddings_query
|
|
325
311
|
+ """
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
+ limit_query
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
records, _, _ = await driver.execute_query(
|
|
332
|
-
query,
|
|
312
|
+
ORDER BY e.uuid DESC
|
|
313
|
+
"""
|
|
314
|
+
+ limit_query,
|
|
333
315
|
group_ids=group_ids,
|
|
334
316
|
uuid=uuid_cursor,
|
|
335
317
|
limit=limit,
|
|
@@ -344,13 +326,15 @@ class EntityEdge(Edge):
|
|
|
344
326
|
|
|
345
327
|
@classmethod
|
|
346
328
|
async def get_by_node_uuid(cls, driver: GraphDriver, node_uuid: str):
|
|
347
|
-
|
|
329
|
+
records, _, _ = await driver.execute_query(
|
|
348
330
|
"""
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
331
|
+
MATCH (n:Entity {uuid: $node_uuid})-[e:RELATES_TO]-(m:Entity)
|
|
332
|
+
RETURN
|
|
333
|
+
"""
|
|
334
|
+
+ ENTITY_EDGE_RETURN,
|
|
335
|
+
node_uuid=node_uuid,
|
|
336
|
+
routing_='r',
|
|
352
337
|
)
|
|
353
|
-
records, _, _ = await driver.execute_query(query, node_uuid=node_uuid, routing_='r')
|
|
354
338
|
|
|
355
339
|
edges = [get_entity_edge_from_record(record) for record in records]
|
|
356
340
|
|
|
@@ -360,7 +344,7 @@ class EntityEdge(Edge):
|
|
|
360
344
|
class CommunityEdge(Edge):
|
|
361
345
|
async def save(self, driver: GraphDriver):
|
|
362
346
|
result = await driver.execute_query(
|
|
363
|
-
|
|
347
|
+
get_community_edge_save_query(driver.provider),
|
|
364
348
|
community_uuid=self.source_node_uuid,
|
|
365
349
|
entity_uuid=self.target_node_uuid,
|
|
366
350
|
uuid=self.uuid,
|
|
@@ -376,14 +360,10 @@ class CommunityEdge(Edge):
|
|
|
376
360
|
async def get_by_uuid(cls, driver: GraphDriver, uuid: str):
|
|
377
361
|
records, _, _ = await driver.execute_query(
|
|
378
362
|
"""
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
n.uuid AS source_node_uuid,
|
|
384
|
-
m.uuid AS target_node_uuid,
|
|
385
|
-
e.created_at AS created_at
|
|
386
|
-
""",
|
|
363
|
+
MATCH (n:Community)-[e:HAS_MEMBER {uuid: $uuid}]->(m)
|
|
364
|
+
RETURN
|
|
365
|
+
"""
|
|
366
|
+
+ COMMUNITY_EDGE_RETURN,
|
|
387
367
|
uuid=uuid,
|
|
388
368
|
routing_='r',
|
|
389
369
|
)
|
|
@@ -396,15 +376,11 @@ class CommunityEdge(Edge):
|
|
|
396
376
|
async def get_by_uuids(cls, driver: GraphDriver, uuids: list[str]):
|
|
397
377
|
records, _, _ = await driver.execute_query(
|
|
398
378
|
"""
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
n.uuid AS source_node_uuid,
|
|
405
|
-
m.uuid AS target_node_uuid,
|
|
406
|
-
e.created_at AS created_at
|
|
407
|
-
""",
|
|
379
|
+
MATCH (n:Community)-[e:HAS_MEMBER]->(m)
|
|
380
|
+
WHERE e.uuid IN $uuids
|
|
381
|
+
RETURN
|
|
382
|
+
"""
|
|
383
|
+
+ COMMUNITY_EDGE_RETURN,
|
|
408
384
|
uuids=uuids,
|
|
409
385
|
routing_='r',
|
|
410
386
|
)
|
|
@@ -426,19 +402,17 @@ class CommunityEdge(Edge):
|
|
|
426
402
|
|
|
427
403
|
records, _, _ = await driver.execute_query(
|
|
428
404
|
"""
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
405
|
+
MATCH (n:Community)-[e:HAS_MEMBER]->(m)
|
|
406
|
+
WHERE e.group_id IN $group_ids
|
|
407
|
+
"""
|
|
432
408
|
+ cursor_query
|
|
433
409
|
+ """
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
ORDER BY e.uuid DESC
|
|
441
|
-
"""
|
|
410
|
+
RETURN
|
|
411
|
+
"""
|
|
412
|
+
+ COMMUNITY_EDGE_RETURN
|
|
413
|
+
+ """
|
|
414
|
+
ORDER BY e.uuid DESC
|
|
415
|
+
"""
|
|
442
416
|
+ limit_query,
|
|
443
417
|
group_ids=group_ids,
|
|
444
418
|
uuid=uuid_cursor,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database query utilities for different graph database backends.
|
|
3
|
+
|
|
4
|
+
This module provides database-agnostic query generation for Neo4j and FalkorDB,
|
|
5
|
+
supporting index creation, fulltext search, and bulk operations.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing_extensions import LiteralString
|
|
9
|
+
|
|
10
|
+
from graphiti_core.driver.driver import GraphProvider
|
|
11
|
+
|
|
12
|
+
# Mapping from Neo4j fulltext index names to FalkorDB node labels
|
|
13
|
+
NEO4J_TO_FALKORDB_MAPPING = {
|
|
14
|
+
'node_name_and_summary': 'Entity',
|
|
15
|
+
'community_name': 'Community',
|
|
16
|
+
'episode_content': 'Episodic',
|
|
17
|
+
'edge_name_and_fact': 'RELATES_TO',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_range_indices(provider: GraphProvider) -> list[LiteralString]:
|
|
22
|
+
if provider == GraphProvider.FALKORDB:
|
|
23
|
+
return [
|
|
24
|
+
# Entity node
|
|
25
|
+
'CREATE INDEX FOR (n:Entity) ON (n.uuid, n.group_id, n.name, n.created_at)',
|
|
26
|
+
# Episodic node
|
|
27
|
+
'CREATE INDEX FOR (n:Episodic) ON (n.uuid, n.group_id, n.created_at, n.valid_at)',
|
|
28
|
+
# Community node
|
|
29
|
+
'CREATE INDEX FOR (n:Community) ON (n.uuid)',
|
|
30
|
+
# RELATES_TO edge
|
|
31
|
+
'CREATE INDEX FOR ()-[e:RELATES_TO]-() ON (e.uuid, e.group_id, e.name, e.created_at, e.expired_at, e.valid_at, e.invalid_at)',
|
|
32
|
+
# MENTIONS edge
|
|
33
|
+
'CREATE INDEX FOR ()-[e:MENTIONS]-() ON (e.uuid, e.group_id)',
|
|
34
|
+
# HAS_MEMBER edge
|
|
35
|
+
'CREATE INDEX FOR ()-[e:HAS_MEMBER]-() ON (e.uuid)',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
return [
|
|
39
|
+
'CREATE INDEX entity_uuid IF NOT EXISTS FOR (n:Entity) ON (n.uuid)',
|
|
40
|
+
'CREATE INDEX episode_uuid IF NOT EXISTS FOR (n:Episodic) ON (n.uuid)',
|
|
41
|
+
'CREATE INDEX community_uuid IF NOT EXISTS FOR (n:Community) ON (n.uuid)',
|
|
42
|
+
'CREATE INDEX relation_uuid IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.uuid)',
|
|
43
|
+
'CREATE INDEX mention_uuid IF NOT EXISTS FOR ()-[e:MENTIONS]-() ON (e.uuid)',
|
|
44
|
+
'CREATE INDEX has_member_uuid IF NOT EXISTS FOR ()-[e:HAS_MEMBER]-() ON (e.uuid)',
|
|
45
|
+
'CREATE INDEX entity_group_id IF NOT EXISTS FOR (n:Entity) ON (n.group_id)',
|
|
46
|
+
'CREATE INDEX episode_group_id IF NOT EXISTS FOR (n:Episodic) ON (n.group_id)',
|
|
47
|
+
'CREATE INDEX relation_group_id IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.group_id)',
|
|
48
|
+
'CREATE INDEX mention_group_id IF NOT EXISTS FOR ()-[e:MENTIONS]-() ON (e.group_id)',
|
|
49
|
+
'CREATE INDEX name_entity_index IF NOT EXISTS FOR (n:Entity) ON (n.name)',
|
|
50
|
+
'CREATE INDEX created_at_entity_index IF NOT EXISTS FOR (n:Entity) ON (n.created_at)',
|
|
51
|
+
'CREATE INDEX created_at_episodic_index IF NOT EXISTS FOR (n:Episodic) ON (n.created_at)',
|
|
52
|
+
'CREATE INDEX valid_at_episodic_index IF NOT EXISTS FOR (n:Episodic) ON (n.valid_at)',
|
|
53
|
+
'CREATE INDEX name_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.name)',
|
|
54
|
+
'CREATE INDEX created_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.created_at)',
|
|
55
|
+
'CREATE INDEX expired_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.expired_at)',
|
|
56
|
+
'CREATE INDEX valid_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.valid_at)',
|
|
57
|
+
'CREATE INDEX invalid_at_edge_index IF NOT EXISTS FOR ()-[e:RELATES_TO]-() ON (e.invalid_at)',
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_fulltext_indices(provider: GraphProvider) -> list[LiteralString]:
|
|
62
|
+
if provider == GraphProvider.FALKORDB:
|
|
63
|
+
return [
|
|
64
|
+
"""CREATE FULLTEXT INDEX FOR (e:Episodic) ON (e.content, e.source, e.source_description, e.group_id)""",
|
|
65
|
+
"""CREATE FULLTEXT INDEX FOR (n:Entity) ON (n.name, n.summary, n.group_id)""",
|
|
66
|
+
"""CREATE FULLTEXT INDEX FOR (n:Community) ON (n.name, n.group_id)""",
|
|
67
|
+
"""CREATE FULLTEXT INDEX FOR ()-[e:RELATES_TO]-() ON (e.name, e.fact, e.group_id)""",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
return [
|
|
71
|
+
"""CREATE FULLTEXT INDEX episode_content IF NOT EXISTS
|
|
72
|
+
FOR (e:Episodic) ON EACH [e.content, e.source, e.source_description, e.group_id]""",
|
|
73
|
+
"""CREATE FULLTEXT INDEX node_name_and_summary IF NOT EXISTS
|
|
74
|
+
FOR (n:Entity) ON EACH [n.name, n.summary, n.group_id]""",
|
|
75
|
+
"""CREATE FULLTEXT INDEX community_name IF NOT EXISTS
|
|
76
|
+
FOR (n:Community) ON EACH [n.name, n.group_id]""",
|
|
77
|
+
"""CREATE FULLTEXT INDEX edge_name_and_fact IF NOT EXISTS
|
|
78
|
+
FOR ()-[e:RELATES_TO]-() ON EACH [e.name, e.fact, e.group_id]""",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_nodes_query(provider: GraphProvider, name: str = '', query: str | None = None) -> str:
|
|
83
|
+
if provider == GraphProvider.FALKORDB:
|
|
84
|
+
label = NEO4J_TO_FALKORDB_MAPPING[name]
|
|
85
|
+
return f"CALL db.idx.fulltext.queryNodes('{label}', {query})"
|
|
86
|
+
|
|
87
|
+
return f'CALL db.index.fulltext.queryNodes("{name}", {query}, {{limit: $limit}})'
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_vector_cosine_func_query(vec1, vec2, provider: GraphProvider) -> str:
|
|
91
|
+
if provider == GraphProvider.FALKORDB:
|
|
92
|
+
# FalkorDB uses a different syntax for regular cosine similarity and Neo4j uses normalized cosine similarity
|
|
93
|
+
return f'(2 - vec.cosineDistance({vec1}, vecf32({vec2})))/2'
|
|
94
|
+
|
|
95
|
+
return f'vector.similarity.cosine({vec1}, {vec2})'
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_relationships_query(name: str, provider: GraphProvider) -> str:
|
|
99
|
+
if provider == GraphProvider.FALKORDB:
|
|
100
|
+
label = NEO4J_TO_FALKORDB_MAPPING[name]
|
|
101
|
+
return f"CALL db.idx.fulltext.queryRelationships('{label}', $query)"
|
|
102
|
+
|
|
103
|
+
return f'CALL db.index.fulltext.queryRelationships("{name}", $query, {{limit: $limit}})'
|