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.

Files changed (190) hide show
  1. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/unit_tests.yml +27 -0
  2. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/PKG-INFO +11 -3
  3. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/README.md +10 -2
  4. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/driver/driver.py +20 -2
  5. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/driver/falkordb_driver.py +16 -9
  6. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/driver/neo4j_driver.py +8 -6
  7. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/edges.py +73 -99
  8. graphiti_core-0.18.1/graphiti_core/graph_queries.py +103 -0
  9. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/graphiti.py +23 -8
  10. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/helpers.py +3 -2
  11. graphiti_core-0.18.1/graphiti_core/models/edges/edge_db_queries.py +130 -0
  12. graphiti_core-0.18.1/graphiti_core/models/nodes/node_db_queries.py +133 -0
  13. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/nodes.py +113 -128
  14. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/dedupe_nodes.py +1 -1
  15. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/extract_nodes.py +12 -10
  16. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_filters.py +5 -5
  17. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_utils.py +139 -184
  18. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/bulk_utils.py +3 -5
  19. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/community_operations.py +11 -7
  20. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/edge_operations.py +19 -50
  21. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/graph_data_operations.py +14 -29
  22. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/node_operations.py +11 -55
  23. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/pyproject.toml +4 -4
  24. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/signatures/version1/cla.json +16 -0
  25. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/driver/test_falkordb_driver.py +10 -15
  26. graphiti_core-0.18.1/tests/helpers_test.py +96 -0
  27. graphiti_core-0.18.1/tests/test_edge_int.py +384 -0
  28. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/test_entity_exclusion_int.py +36 -19
  29. graphiti_core-0.18.1/tests/test_graphiti_int.py +78 -0
  30. graphiti_core-0.18.1/tests/test_node_int.py +243 -0
  31. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/uv.lock +1 -1
  32. graphiti_core-0.18.0/graphiti_core/graph_queries.py +0 -149
  33. graphiti_core-0.18.0/graphiti_core/models/edges/edge_db_queries.py +0 -56
  34. graphiti_core-0.18.0/graphiti_core/models/nodes/node_db_queries.py +0 -52
  35. graphiti_core-0.18.0/tests/helpers_test.py +0 -38
  36. graphiti_core-0.18.0/tests/test_graphiti_falkordb_int.py +0 -164
  37. graphiti_core-0.18.0/tests/test_graphiti_int.py +0 -149
  38. graphiti_core-0.18.0/tests/test_node_falkordb_int.py +0 -139
  39. graphiti_core-0.18.0/tests/test_node_int.py +0 -122
  40. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.env.example +0 -0
  41. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  42. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/dependabot.yml +0 -0
  43. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/pull_request_template.md +0 -0
  44. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/secret_scanning.yml +0 -0
  45. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/cla.yml +0 -0
  46. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/claude-code-review.yml +0 -0
  47. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/claude.yml +0 -0
  48. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/codeql.yml +0 -0
  49. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/lint.yml +0 -0
  50. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/mcp-server-docker.yml +0 -0
  51. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/release-graphiti-core.yml +0 -0
  52. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.github/workflows/typecheck.yml +0 -0
  53. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/.gitignore +0 -0
  54. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/CLAUDE.md +0 -0
  55. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/CODE_OF_CONDUCT.md +0 -0
  56. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/CONTRIBUTING.md +0 -0
  57. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/Dockerfile +0 -0
  58. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/LICENSE +0 -0
  59. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/Makefile +0 -0
  60. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/SECURITY.md +0 -0
  61. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/Zep-CLA.md +0 -0
  62. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/conftest.py +0 -0
  63. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/depot.json +0 -0
  64. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/docker-compose.test.yml +0 -0
  65. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/docker-compose.yml +0 -0
  66. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/ellipsis.yaml +0 -0
  67. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/data/manybirds_products.json +0 -0
  68. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/ecommerce/runner.ipynb +0 -0
  69. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/ecommerce/runner.py +0 -0
  70. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/langgraph-agent/agent.ipynb +0 -0
  71. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/langgraph-agent/tinybirds-jess.png +0 -0
  72. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/podcast/podcast_runner.py +0 -0
  73. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/podcast/podcast_transcript.txt +0 -0
  74. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/podcast/transcript_parser.py +0 -0
  75. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/quickstart/README.md +0 -0
  76. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/quickstart/quickstart_falkordb.py +0 -0
  77. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/quickstart/quickstart_neo4j.py +0 -0
  78. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/quickstart/requirements.txt +0 -0
  79. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/wizard_of_oz/parser.py +0 -0
  80. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/wizard_of_oz/runner.py +0 -0
  81. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/examples/wizard_of_oz/woo.txt +0 -0
  82. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/__init__.py +0 -0
  83. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/__init__.py +0 -0
  84. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/bge_reranker_client.py +0 -0
  85. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/client.py +0 -0
  86. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/gemini_reranker_client.py +0 -0
  87. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/cross_encoder/openai_reranker_client.py +0 -0
  88. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/driver/__init__.py +0 -0
  89. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/__init__.py +0 -0
  90. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/azure_openai.py +0 -0
  91. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/client.py +0 -0
  92. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/gemini.py +0 -0
  93. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/openai.py +0 -0
  94. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/embedder/voyage.py +0 -0
  95. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/errors.py +0 -0
  96. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/graphiti_types.py +0 -0
  97. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/__init__.py +0 -0
  98. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/anthropic_client.py +0 -0
  99. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/azure_openai_client.py +0 -0
  100. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/client.py +0 -0
  101. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/config.py +0 -0
  102. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/errors.py +0 -0
  103. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/gemini_client.py +0 -0
  104. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/groq_client.py +0 -0
  105. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/openai_base_client.py +0 -0
  106. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/openai_client.py +0 -0
  107. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/openai_generic_client.py +0 -0
  108. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/llm_client/utils.py +0 -0
  109. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/models/__init__.py +0 -0
  110. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/models/edges/__init__.py +0 -0
  111. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/models/nodes/__init__.py +0 -0
  112. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/__init__.py +0 -0
  113. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/dedupe_edges.py +0 -0
  114. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/eval.py +0 -0
  115. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/extract_edge_dates.py +0 -0
  116. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/extract_edges.py +4 -4
  117. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/invalidate_edges.py +0 -0
  118. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/lib.py +0 -0
  119. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/models.py +0 -0
  120. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/prompt_helpers.py +0 -0
  121. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/prompts/summarize_nodes.py +0 -0
  122. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/py.typed +0 -0
  123. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/__init__.py +0 -0
  124. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search.py +0 -0
  125. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_config.py +0 -0
  126. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_config_recipes.py +0 -0
  127. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/search/search_helpers.py +0 -0
  128. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/telemetry/__init__.py +0 -0
  129. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/telemetry/telemetry.py +0 -0
  130. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/__init__.py +0 -0
  131. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/datetime_utils.py +0 -0
  132. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/__init__.py +0 -0
  133. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/temporal_operations.py +0 -0
  134. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/maintenance/utils.py +0 -0
  135. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/graphiti_core/utils/ontology_utils/entity_types_utils.py +0 -0
  136. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/images/arxiv-screenshot.png +0 -0
  137. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/images/graphiti-graph-intro.gif +0 -0
  138. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/images/graphiti-intro-slides-stock-2.gif +0 -0
  139. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/images/simple_graph.svg +0 -0
  140. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/.env.example +0 -0
  141. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/.python-version +0 -0
  142. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/Dockerfile +0 -0
  143. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/README.md +0 -0
  144. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/cursor_rules.md +0 -0
  145. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/docker-compose.yml +0 -0
  146. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/graphiti_mcp_server.py +0 -0
  147. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/mcp_config_sse_example.json +0 -0
  148. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/mcp_config_stdio_example.json +0 -0
  149. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/pyproject.toml +0 -0
  150. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/mcp_server/uv.lock +0 -0
  151. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/poetry.lock +0 -0
  152. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/py.typed +0 -0
  153. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/pytest.ini +0 -0
  154. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/.env.example +0 -0
  155. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/Makefile +0 -0
  156. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/README.md +0 -0
  157. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/__init__.py +0 -0
  158. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/config.py +0 -0
  159. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/dto/__init__.py +0 -0
  160. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/dto/common.py +0 -0
  161. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/dto/ingest.py +0 -0
  162. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/dto/retrieve.py +0 -0
  163. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/main.py +0 -0
  164. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/routers/__init__.py +0 -0
  165. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/routers/ingest.py +0 -0
  166. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/routers/retrieve.py +0 -0
  167. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/graph_service/zep_graphiti.py +0 -0
  168. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/pyproject.toml +0 -0
  169. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/server/uv.lock +0 -0
  170. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/cross_encoder/test_bge_reranker_client.py +0 -0
  171. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/cross_encoder/test_gemini_reranker_client.py +0 -0
  172. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/driver/__init__.py +0 -0
  173. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/embedder/embedder_fixtures.py +0 -0
  174. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/embedder/test_gemini.py +0 -0
  175. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/embedder/test_openai.py +0 -0
  176. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/embedder/test_voyage.py +0 -0
  177. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/data/longmemeval_data/README.md +0 -0
  178. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/data/longmemeval_data/longmemeval_oracle.json +0 -0
  179. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/eval_cli.py +0 -0
  180. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/eval_e2e_graph_building.py +0 -0
  181. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/pytest.ini +0 -0
  182. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/evals/utils.py +0 -0
  183. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_anthropic_client.py +0 -0
  184. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_anthropic_client_int.py +0 -0
  185. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_client.py +0 -0
  186. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_errors.py +0 -0
  187. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/llm_client/test_gemini_client.py +0 -0
  188. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/utils/maintenance/test_edge_operations.py +0 -0
  189. {graphiti_core-0.18.0 → graphiti_core-0.18.1}/tests/utils/maintenance/test_temporal_operations_int.py +0 -0
  190. {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.0
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 Memory
109
+ ## Graphiti and Zep's Context Engineering Platform.
110
110
 
111
- Graphiti powers the core of [Zep's memory layer](https://www.getzep.com) for AI Agents.
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 Memory
57
+ ## Graphiti and Zep's Context Engineering Platform.
58
58
 
59
- Graphiti powers the core of [Zep's memory layer](https://www.getzep.com) for AI Agents.
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: str
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, database_: str | None = None) -> Coroutine:
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: str = 'falkordb'
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
- graph_name = kwargs.pop('database_', self._database)
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, database_: str | None = None) -> None:
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: str = 'neo4j'
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
- result = await self.client.execute_query(cypher_query_, parameters_=params, **kwargs)
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, database_: str | None = None) -> Coroutine[Any, Any, EagerResult]:
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
- COMMUNITY_EDGE_SAVE,
33
- ENTITY_EDGE_SAVE,
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
- MATCH (n)-[e:MENTIONS|RELATES_TO|HAS_MEMBER {uuid: $uuid}]->(m)
70
- DELETE e
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
- MATCH (n:Episodic)-[e:MENTIONS {uuid: $uuid}]->(m:Entity)
111
- RETURN
112
- e.uuid As uuid,
113
- e.group_id AS group_id,
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
- MATCH (n:Episodic)-[e:MENTIONS]->(m:Entity)
133
- WHERE e.uuid IN $uuids
134
- RETURN
135
- e.uuid As uuid,
136
- e.group_id AS group_id,
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
- MATCH (n:Episodic)-[e:MENTIONS]->(m:Entity)
165
- WHERE e.group_id IN $group_ids
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
- RETURN
170
- e.uuid As uuid,
171
- e.group_id AS group_id,
172
- n.uuid AS source_node_uuid,
173
- m.uuid AS target_node_uuid,
174
- e.created_at AS created_at
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
- query: LiteralString = """
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
- records, _, _ = await driver.execute_query(query, uuid=self.uuid, routing_='r')
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
- ENTITY_EDGE_SAVE,
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
- MATCH (n:Entity)-[e:RELATES_TO {uuid: $uuid}]->(m:Entity)
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
- MATCH (n:Entity)-[e:RELATES_TO]->(m:Entity)
287
- WHERE e.uuid IN $uuids
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
- query: LiteralString = (
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
- ORDER BY e.uuid DESC
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
- query: LiteralString = (
329
+ records, _, _ = await driver.execute_query(
348
330
  """
349
- MATCH (n:Entity {uuid: $node_uuid})-[e:RELATES_TO]-(m:Entity)
350
- """
351
- + ENTITY_EDGE_RETURN
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
- COMMUNITY_EDGE_SAVE,
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
- MATCH (n:Community)-[e:HAS_MEMBER {uuid: $uuid}]->(m:Entity | Community)
380
- RETURN
381
- e.uuid As uuid,
382
- e.group_id AS group_id,
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
- MATCH (n:Community)-[e:HAS_MEMBER]->(m:Entity | Community)
400
- WHERE e.uuid IN $uuids
401
- RETURN
402
- e.uuid As uuid,
403
- e.group_id AS group_id,
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
- MATCH (n:Community)-[e:HAS_MEMBER]->(m:Entity | Community)
430
- WHERE e.group_id IN $group_ids
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
- RETURN
435
- e.uuid As uuid,
436
- e.group_id AS group_id,
437
- n.uuid AS source_node_uuid,
438
- m.uuid AS target_node_uuid,
439
- e.created_at AS created_at
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}})'