graphiti-core 0.30.0rc3__tar.gz → 0.30.0rc5__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 (192) hide show
  1. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/PKG-INFO +4 -1
  2. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/graphiti.py +1 -0
  3. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/bulk_utils.py +1 -0
  4. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/edge_operations.py +75 -6
  5. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/pyproject.toml +4 -1
  6. graphiti_core-0.30.0rc5/tests/utils/maintenance/test_edge_operations.py +436 -0
  7. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/uv.lock +7 -1
  8. graphiti_core-0.30.0rc3/tests/utils/maintenance/test_edge_operations.py +0 -144
  9. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.env.example +0 -0
  10. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  11. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/dependabot.yml +0 -0
  12. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/pull_request_template.md +0 -0
  13. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/secret_scanning.yml +0 -0
  14. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/ai-moderator.yml +0 -0
  15. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/cla.yml +0 -0
  16. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/claude-code-review.yml +0 -0
  17. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/claude.yml +0 -0
  18. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/codeql.yml +0 -0
  19. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/lint.yml +0 -0
  20. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/mcp-server-docker.yml +0 -0
  21. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/release-graphiti-core.yml +0 -0
  22. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/typecheck.yml +0 -0
  23. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/unit_tests.yml +0 -0
  24. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.gitignore +0 -0
  25. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/AGENTS.md +0 -0
  26. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/CLAUDE.md +0 -0
  27. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/CODE_OF_CONDUCT.md +0 -0
  28. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/CONTRIBUTING.md +0 -0
  29. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/Dockerfile +0 -0
  30. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/LICENSE +0 -0
  31. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/Makefile +0 -0
  32. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/README.md +0 -0
  33. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/SECURITY.md +0 -0
  34. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/Zep-CLA.md +0 -0
  35. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/conftest.py +0 -0
  36. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/depot.json +0 -0
  37. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/docker-compose.test.yml +0 -0
  38. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/docker-compose.yml +0 -0
  39. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/ellipsis.yaml +0 -0
  40. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/data/manybirds_products.json +0 -0
  41. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/ecommerce/runner.ipynb +0 -0
  42. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/ecommerce/runner.py +0 -0
  43. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/langgraph-agent/agent.ipynb +0 -0
  44. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/langgraph-agent/tinybirds-jess.png +0 -0
  45. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/podcast/podcast_runner.py +0 -0
  46. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/podcast/podcast_transcript.txt +0 -0
  47. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/podcast/transcript_parser.py +0 -0
  48. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/README.md +0 -0
  49. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/quickstart_falkordb.py +0 -0
  50. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/quickstart_neo4j.py +0 -0
  51. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/quickstart_neptune.py +0 -0
  52. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/requirements.txt +0 -0
  53. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/wizard_of_oz/parser.py +0 -0
  54. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/wizard_of_oz/runner.py +0 -0
  55. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/wizard_of_oz/woo.txt +0 -0
  56. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/__init__.py +0 -0
  57. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/__init__.py +0 -0
  58. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/bge_reranker_client.py +0 -0
  59. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/client.py +0 -0
  60. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/gemini_reranker_client.py +0 -0
  61. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/openai_reranker_client.py +0 -0
  62. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/__init__.py +0 -0
  63. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/driver.py +0 -0
  64. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/falkordb_driver.py +0 -0
  65. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/kuzu_driver.py +0 -0
  66. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/neo4j_driver.py +0 -0
  67. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/neptune_driver.py +0 -0
  68. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/edges.py +0 -0
  69. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/__init__.py +0 -0
  70. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/azure_openai.py +0 -0
  71. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/client.py +0 -0
  72. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/gemini.py +0 -0
  73. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/openai.py +0 -0
  74. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/voyage.py +0 -0
  75. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/errors.py +0 -0
  76. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/graph_queries.py +0 -0
  77. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/graphiti_types.py +0 -0
  78. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/helpers.py +0 -0
  79. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/__init__.py +0 -0
  80. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/anthropic_client.py +0 -0
  81. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/azure_openai_client.py +0 -0
  82. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/client.py +0 -0
  83. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/config.py +0 -0
  84. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/errors.py +0 -0
  85. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/gemini_client.py +0 -0
  86. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/groq_client.py +0 -0
  87. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/openai_base_client.py +0 -0
  88. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/openai_client.py +0 -0
  89. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/openai_generic_client.py +0 -0
  90. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/utils.py +0 -0
  91. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/migrations/__init__.py +0 -0
  92. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/__init__.py +0 -0
  93. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/edges/__init__.py +0 -0
  94. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/edges/edge_db_queries.py +0 -0
  95. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/nodes/__init__.py +0 -0
  96. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/nodes/node_db_queries.py +0 -0
  97. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/nodes.py +0 -0
  98. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/__init__.py +0 -0
  99. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/dedupe_edges.py +0 -0
  100. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/dedupe_nodes.py +0 -0
  101. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/eval.py +0 -0
  102. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/extract_edge_dates.py +0 -0
  103. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/extract_edges.py +0 -0
  104. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/extract_nodes.py +0 -0
  105. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/invalidate_edges.py +0 -0
  106. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/lib.py +0 -0
  107. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/models.py +0 -0
  108. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/prompt_helpers.py +0 -0
  109. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/summarize_nodes.py +0 -0
  110. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/py.typed +0 -0
  111. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/__init__.py +0 -0
  112. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search.py +0 -0
  113. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_config.py +0 -0
  114. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_config_recipes.py +0 -0
  115. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_filters.py +0 -0
  116. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_helpers.py +0 -0
  117. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_utils.py +0 -0
  118. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/telemetry/__init__.py +0 -0
  119. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/telemetry/telemetry.py +0 -0
  120. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/__init__.py +0 -0
  121. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/datetime_utils.py +0 -0
  122. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/__init__.py +0 -0
  123. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/community_operations.py +0 -0
  124. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/dedup_helpers.py +0 -0
  125. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/graph_data_operations.py +0 -0
  126. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/node_operations.py +0 -0
  127. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/temporal_operations.py +0 -0
  128. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/utils.py +0 -0
  129. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/ontology_utils/entity_types_utils.py +0 -0
  130. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/images/arxiv-screenshot.png +0 -0
  131. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/images/graphiti-graph-intro.gif +0 -0
  132. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/images/graphiti-intro-slides-stock-2.gif +0 -0
  133. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/images/simple_graph.svg +0 -0
  134. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/.env.example +0 -0
  135. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/.python-version +0 -0
  136. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/Dockerfile +0 -0
  137. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/README.md +0 -0
  138. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/cursor_rules.md +0 -0
  139. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/docker-compose.yml +0 -0
  140. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/graphiti_mcp_server.py +0 -0
  141. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/mcp_config_sse_example.json +0 -0
  142. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/mcp_config_stdio_example.json +0 -0
  143. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/pyproject.toml +0 -0
  144. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/uv.lock +0 -0
  145. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/py.typed +0 -0
  146. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/pytest.ini +0 -0
  147. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/.env.example +0 -0
  148. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/Makefile +0 -0
  149. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/README.md +0 -0
  150. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/__init__.py +0 -0
  151. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/config.py +0 -0
  152. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/dto/__init__.py +0 -0
  153. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/dto/common.py +0 -0
  154. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/dto/ingest.py +0 -0
  155. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/dto/retrieve.py +0 -0
  156. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/main.py +0 -0
  157. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/routers/__init__.py +0 -0
  158. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/routers/ingest.py +0 -0
  159. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/routers/retrieve.py +0 -0
  160. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/zep_graphiti.py +0 -0
  161. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/pyproject.toml +0 -0
  162. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/uv.lock +0 -0
  163. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/signatures/version1/cla.json +0 -0
  164. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/cross_encoder/test_bge_reranker_client.py +0 -0
  165. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/cross_encoder/test_gemini_reranker_client.py +0 -0
  166. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/driver/__init__.py +0 -0
  167. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/driver/test_falkordb_driver.py +0 -0
  168. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/embedder/embedder_fixtures.py +0 -0
  169. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/embedder/test_gemini.py +0 -0
  170. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/embedder/test_openai.py +0 -0
  171. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/embedder/test_voyage.py +0 -0
  172. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/data/longmemeval_data/README.md +0 -0
  173. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/data/longmemeval_data/longmemeval_oracle.json +0 -0
  174. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/eval_cli.py +0 -0
  175. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/eval_e2e_graph_building.py +0 -0
  176. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/pytest.ini +0 -0
  177. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/utils.py +0 -0
  178. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/helpers_test.py +0 -0
  179. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_anthropic_client.py +0 -0
  180. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_anthropic_client_int.py +0 -0
  181. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_client.py +0 -0
  182. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_errors.py +0 -0
  183. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_gemini_client.py +0 -0
  184. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_edge_int.py +0 -0
  185. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_entity_exclusion_int.py +0 -0
  186. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_graphiti_int.py +0 -0
  187. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_graphiti_mock.py +0 -0
  188. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_node_int.py +0 -0
  189. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/utils/maintenance/test_bulk_utils.py +0 -0
  190. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/utils/maintenance/test_node_operations.py +0 -0
  191. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/utils/maintenance/test_temporal_operations_int.py +0 -0
  192. {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/utils/search/search_utils_test.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphiti-core
3
- Version: 0.30.0rc3
3
+ Version: 0.30.0rc5
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
@@ -20,6 +20,7 @@ Provides-Extra: anthropic
20
20
  Requires-Dist: anthropic>=0.49.0; extra == 'anthropic'
21
21
  Provides-Extra: dev
22
22
  Requires-Dist: anthropic>=0.49.0; extra == 'dev'
23
+ Requires-Dist: boto3>=1.39.16; extra == 'dev'
23
24
  Requires-Dist: diskcache-stubs>=5.6.3.6.20240818; extra == 'dev'
24
25
  Requires-Dist: falkordb<2.0.0,>=1.1.2; extra == 'dev'
25
26
  Requires-Dist: google-genai>=1.8.0; extra == 'dev'
@@ -28,9 +29,11 @@ Requires-Dist: ipykernel>=6.29.5; extra == 'dev'
28
29
  Requires-Dist: jupyterlab>=4.2.4; extra == 'dev'
29
30
  Requires-Dist: kuzu>=0.11.2; extra == 'dev'
30
31
  Requires-Dist: langchain-anthropic>=0.2.4; extra == 'dev'
32
+ Requires-Dist: langchain-aws>=0.2.29; extra == 'dev'
31
33
  Requires-Dist: langchain-openai>=0.2.6; extra == 'dev'
32
34
  Requires-Dist: langgraph>=0.2.15; extra == 'dev'
33
35
  Requires-Dist: langsmith>=0.1.108; extra == 'dev'
36
+ Requires-Dist: opensearch-py>=3.0.0; extra == 'dev'
34
37
  Requires-Dist: pyright>=1.1.404; extra == 'dev'
35
38
  Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
36
39
  Requires-Dist: pytest-xdist>=3.6.1; extra == 'dev'
@@ -1070,6 +1070,7 @@ class Graphiti:
1070
1070
  group_id=edge.group_id,
1071
1071
  ),
1072
1072
  None,
1073
+ None,
1073
1074
  self.ensure_ascii,
1074
1075
  )
1075
1076
 
@@ -477,6 +477,7 @@ async def dedupe_edges_bulk(
477
477
  candidates,
478
478
  episode,
479
479
  edge_types,
480
+ set(edge_types),
480
481
  clients.ensure_ascii,
481
482
  )
482
483
  for episode, edge, candidates in dedupe_tuples
@@ -43,6 +43,8 @@ from graphiti_core.search.search_filters import SearchFilters
43
43
  from graphiti_core.utils.datetime_utils import ensure_utc, utc_now
44
44
  from graphiti_core.utils.maintenance.dedup_helpers import _normalize_string_exact
45
45
 
46
+ DEFAULT_EDGE_NAME = 'RELATES_TO'
47
+
46
48
  logger = logging.getLogger(__name__)
47
49
 
48
50
 
@@ -281,8 +283,12 @@ async def resolve_extracted_edges(
281
283
  # Build entity hash table
282
284
  uuid_entity_map: dict[str, EntityNode] = {entity.uuid: entity for entity in entities}
283
285
 
284
- # Determine which edge types are relevant for each edge
286
+ # Determine which edge types are relevant for each edge.
287
+ # `edge_types_lst` stores the subset of custom edge definitions whose
288
+ # node signature matches each extracted edge. Anything outside this subset
289
+ # should only stay on the edge if it is a non-custom (LLM generated) label.
285
290
  edge_types_lst: list[dict[str, type[BaseModel]]] = []
291
+ custom_type_names = set(edge_types or {})
286
292
  for extracted_edge in extracted_edges:
287
293
  source_node = uuid_entity_map.get(extracted_edge.source_node_uuid)
288
294
  target_node = uuid_entity_map.get(extracted_edge.target_node_uuid)
@@ -310,6 +316,20 @@ async def resolve_extracted_edges(
310
316
 
311
317
  edge_types_lst.append(extracted_edge_types)
312
318
 
319
+ for extracted_edge, extracted_edge_types in zip(extracted_edges, edge_types_lst, strict=True):
320
+ allowed_type_names = set(extracted_edge_types)
321
+ is_custom_name = extracted_edge.name in custom_type_names
322
+ if not allowed_type_names:
323
+ # No custom types are valid for this node pairing. Keep LLM generated
324
+ # labels, but flip disallowed custom names back to the default.
325
+ if is_custom_name and extracted_edge.name != DEFAULT_EDGE_NAME:
326
+ extracted_edge.name = DEFAULT_EDGE_NAME
327
+ continue
328
+ if is_custom_name and extracted_edge.name not in allowed_type_names:
329
+ # Custom name exists but it is not permitted for this source/target
330
+ # signature, so fall back to the default edge label.
331
+ extracted_edge.name = DEFAULT_EDGE_NAME
332
+
313
333
  # resolve edges with related edges in the graph and find invalidation candidates
314
334
  results: list[tuple[EntityEdge, list[EntityEdge], list[EntityEdge]]] = list(
315
335
  await semaphore_gather(
@@ -321,6 +341,7 @@ async def resolve_extracted_edges(
321
341
  existing_edges,
322
342
  episode,
323
343
  extracted_edge_types,
344
+ custom_type_names,
324
345
  clients.ensure_ascii,
325
346
  )
326
347
  for extracted_edge, related_edges, existing_edges, extracted_edge_types in zip(
@@ -392,9 +413,38 @@ async def resolve_extracted_edge(
392
413
  related_edges: list[EntityEdge],
393
414
  existing_edges: list[EntityEdge],
394
415
  episode: EpisodicNode,
395
- edge_types: dict[str, type[BaseModel]] | None = None,
416
+ edge_type_candidates: dict[str, type[BaseModel]] | None = None,
417
+ custom_edge_type_names: set[str] | None = None,
396
418
  ensure_ascii: bool = True,
397
419
  ) -> tuple[EntityEdge, list[EntityEdge], list[EntityEdge]]:
420
+ """Resolve an extracted edge against existing graph context.
421
+
422
+ Parameters
423
+ ----------
424
+ llm_client : LLMClient
425
+ Client used to invoke the LLM for deduplication and attribute extraction.
426
+ extracted_edge : EntityEdge
427
+ Newly extracted edge whose canonical representation is being resolved.
428
+ related_edges : list[EntityEdge]
429
+ Candidate edges with identical endpoints used for duplicate detection.
430
+ existing_edges : list[EntityEdge]
431
+ Broader set of edges evaluated for contradiction / invalidation.
432
+ episode : EpisodicNode
433
+ Episode providing content context when extracting edge attributes.
434
+ edge_type_candidates : dict[str, type[BaseModel]] | None
435
+ Custom edge types permitted for the current source/target signature.
436
+ custom_edge_type_names : set[str] | None
437
+ Full catalog of registered custom edge names. Used to distinguish
438
+ between disallowed custom types (which fall back to the default label)
439
+ and ad-hoc labels emitted by the LLM.
440
+ ensure_ascii : bool
441
+ Whether prompt payloads should coerce ASCII output.
442
+
443
+ Returns
444
+ -------
445
+ tuple[EntityEdge, list[EntityEdge], list[EntityEdge]]
446
+ The resolved edge, any duplicates, and edges to invalidate.
447
+ """
398
448
  if len(related_edges) == 0 and len(existing_edges) == 0:
399
449
  return extracted_edge, [], []
400
450
 
@@ -429,9 +479,9 @@ async def resolve_extracted_edge(
429
479
  'fact_type_name': type_name,
430
480
  'fact_type_description': type_model.__doc__,
431
481
  }
432
- for i, (type_name, type_model) in enumerate(edge_types.items())
482
+ for i, (type_name, type_model) in enumerate(edge_type_candidates.items())
433
483
  ]
434
- if edge_types is not None
484
+ if edge_type_candidates is not None
435
485
  else []
436
486
  )
437
487
 
@@ -468,7 +518,16 @@ async def resolve_extracted_edge(
468
518
  ]
469
519
 
470
520
  fact_type: str = response_object.fact_type
471
- if fact_type.upper() != 'DEFAULT' and edge_types is not None:
521
+ candidate_type_names = set(edge_type_candidates or {})
522
+ custom_type_names = custom_edge_type_names or set()
523
+
524
+ is_default_type = fact_type.upper() == 'DEFAULT'
525
+ is_custom_type = fact_type in custom_type_names
526
+ is_allowed_custom_type = fact_type in candidate_type_names
527
+
528
+ if is_allowed_custom_type:
529
+ # The LLM selected a custom type that is allowed for the node pair.
530
+ # Adopt the custom type and, if needed, extract its structured attributes.
472
531
  resolved_edge.name = fact_type
473
532
 
474
533
  edge_attributes_context = {
@@ -478,7 +537,7 @@ async def resolve_extracted_edge(
478
537
  'ensure_ascii': ensure_ascii,
479
538
  }
480
539
 
481
- edge_model = edge_types.get(fact_type)
540
+ edge_model = edge_type_candidates.get(fact_type) if edge_type_candidates else None
482
541
  if edge_model is not None and len(edge_model.model_fields) != 0:
483
542
  edge_attributes_response = await llm_client.generate_response(
484
543
  prompt_library.extract_edges.extract_attributes(edge_attributes_context),
@@ -487,6 +546,16 @@ async def resolve_extracted_edge(
487
546
  )
488
547
 
489
548
  resolved_edge.attributes = edge_attributes_response
549
+ elif not is_default_type and is_custom_type:
550
+ # The LLM picked a custom type that is not allowed for this signature.
551
+ # Reset to the default label and drop any structured attributes.
552
+ resolved_edge.name = DEFAULT_EDGE_NAME
553
+ resolved_edge.attributes = {}
554
+ elif not is_default_type:
555
+ # Non-custom labels are allowed to pass through so long as the LLM does
556
+ # not return the sentinel DEFAULT value.
557
+ resolved_edge.name = fact_type
558
+ resolved_edge.attributes = {}
490
559
 
491
560
  end = time()
492
561
  logger.debug(
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "graphiti-core"
3
3
  description = "A temporal graph building library"
4
- version = "0.30.0pre3"
4
+ version = "0.30.0pre5"
5
5
  authors = [
6
6
  { name = "Paul Paliychuk", email = "paul@getzep.com" },
7
7
  { name = "Preston Rasmussen", email = "preston@getzep.com" },
@@ -42,6 +42,9 @@ dev = [
42
42
  "google-genai>=1.8.0",
43
43
  "falkordb>=1.1.2,<2.0.0",
44
44
  "kuzu>=0.11.2",
45
+ "boto3>=1.39.16",
46
+ "opensearch-py>=3.0.0",
47
+ "langchain-aws>=0.2.29",
45
48
  "ipykernel>=6.29.5",
46
49
  "jupyterlab>=4.2.4",
47
50
  "diskcache-stubs>=5.6.3.6.20240818",
@@ -0,0 +1,436 @@
1
+ from datetime import datetime, timedelta, timezone
2
+ from types import SimpleNamespace
3
+ from unittest.mock import AsyncMock, MagicMock
4
+
5
+ import pytest
6
+ from pydantic import BaseModel
7
+
8
+ from graphiti_core.edges import EntityEdge
9
+ from graphiti_core.nodes import EntityNode, EpisodicNode
10
+ from graphiti_core.search.search_config import SearchResults
11
+ from graphiti_core.utils.maintenance.edge_operations import (
12
+ DEFAULT_EDGE_NAME,
13
+ resolve_extracted_edge,
14
+ resolve_extracted_edges,
15
+ )
16
+
17
+
18
+ @pytest.fixture
19
+ def mock_llm_client():
20
+ client = MagicMock()
21
+ client.generate_response = AsyncMock()
22
+ return client
23
+
24
+
25
+ @pytest.fixture
26
+ def mock_extracted_edge():
27
+ return EntityEdge(
28
+ source_node_uuid='source_uuid',
29
+ target_node_uuid='target_uuid',
30
+ name='test_edge',
31
+ group_id='group_1',
32
+ fact='Test fact',
33
+ episodes=['episode_1'],
34
+ created_at=datetime.now(timezone.utc),
35
+ valid_at=None,
36
+ invalid_at=None,
37
+ )
38
+
39
+
40
+ @pytest.fixture
41
+ def mock_related_edges():
42
+ return [
43
+ EntityEdge(
44
+ source_node_uuid='source_uuid_2',
45
+ target_node_uuid='target_uuid_2',
46
+ name='related_edge',
47
+ group_id='group_1',
48
+ fact='Related fact',
49
+ episodes=['episode_2'],
50
+ created_at=datetime.now(timezone.utc) - timedelta(days=1),
51
+ valid_at=datetime.now(timezone.utc) - timedelta(days=1),
52
+ invalid_at=None,
53
+ )
54
+ ]
55
+
56
+
57
+ @pytest.fixture
58
+ def mock_existing_edges():
59
+ return [
60
+ EntityEdge(
61
+ source_node_uuid='source_uuid_3',
62
+ target_node_uuid='target_uuid_3',
63
+ name='existing_edge',
64
+ group_id='group_1',
65
+ fact='Existing fact',
66
+ episodes=['episode_3'],
67
+ created_at=datetime.now(timezone.utc) - timedelta(days=2),
68
+ valid_at=datetime.now(timezone.utc) - timedelta(days=2),
69
+ invalid_at=None,
70
+ )
71
+ ]
72
+
73
+
74
+ @pytest.fixture
75
+ def mock_current_episode():
76
+ return EpisodicNode(
77
+ uuid='episode_1',
78
+ content='Current episode content',
79
+ valid_at=datetime.now(timezone.utc),
80
+ name='Current Episode',
81
+ group_id='group_1',
82
+ source='message',
83
+ source_description='Test source description',
84
+ )
85
+
86
+
87
+ @pytest.fixture
88
+ def mock_previous_episodes():
89
+ return [
90
+ EpisodicNode(
91
+ uuid='episode_2',
92
+ content='Previous episode content',
93
+ valid_at=datetime.now(timezone.utc) - timedelta(days=1),
94
+ name='Previous Episode',
95
+ group_id='group_1',
96
+ source='message',
97
+ source_description='Test source description',
98
+ )
99
+ ]
100
+
101
+
102
+ # Run the tests
103
+ if __name__ == '__main__':
104
+ pytest.main([__file__])
105
+
106
+
107
+ @pytest.mark.asyncio
108
+ async def test_resolve_extracted_edge_exact_fact_short_circuit(
109
+ mock_llm_client,
110
+ mock_existing_edges,
111
+ mock_current_episode,
112
+ ):
113
+ extracted = EntityEdge(
114
+ source_node_uuid='source_uuid',
115
+ target_node_uuid='target_uuid',
116
+ name='test_edge',
117
+ group_id='group_1',
118
+ fact='Related fact',
119
+ episodes=['episode_1'],
120
+ created_at=datetime.now(timezone.utc),
121
+ valid_at=None,
122
+ invalid_at=None,
123
+ )
124
+
125
+ related_edges = [
126
+ EntityEdge(
127
+ source_node_uuid='source_uuid',
128
+ target_node_uuid='target_uuid',
129
+ name='related_edge',
130
+ group_id='group_1',
131
+ fact=' related FACT ',
132
+ episodes=['episode_2'],
133
+ created_at=datetime.now(timezone.utc) - timedelta(days=1),
134
+ valid_at=None,
135
+ invalid_at=None,
136
+ )
137
+ ]
138
+
139
+ resolved_edge, duplicate_edges, invalidated = await resolve_extracted_edge(
140
+ mock_llm_client,
141
+ extracted,
142
+ related_edges,
143
+ mock_existing_edges,
144
+ mock_current_episode,
145
+ edge_type_candidates=None,
146
+ ensure_ascii=True,
147
+ )
148
+
149
+ assert resolved_edge is related_edges[0]
150
+ assert resolved_edge.episodes.count(mock_current_episode.uuid) == 1
151
+ assert duplicate_edges == []
152
+ assert invalidated == []
153
+ mock_llm_client.generate_response.assert_not_called()
154
+
155
+
156
+ class OccurredAtEdge(BaseModel):
157
+ """Edge model stub for OCCURRED_AT."""
158
+
159
+
160
+ @pytest.mark.asyncio
161
+ async def test_resolve_extracted_edges_resets_unmapped_names(monkeypatch):
162
+ from graphiti_core.utils.maintenance import edge_operations as edge_ops
163
+
164
+ monkeypatch.setattr(edge_ops, 'create_entity_edge_embeddings', AsyncMock(return_value=None))
165
+ monkeypatch.setattr(EntityEdge, 'get_between_nodes', AsyncMock(return_value=[]))
166
+
167
+ async def immediate_gather(*aws, max_coroutines=None):
168
+ return [await aw for aw in aws]
169
+
170
+ monkeypatch.setattr(edge_ops, 'semaphore_gather', immediate_gather)
171
+ monkeypatch.setattr(edge_ops, 'search', AsyncMock(return_value=SearchResults()))
172
+
173
+ llm_client = MagicMock()
174
+ llm_client.generate_response = AsyncMock(
175
+ return_value={
176
+ 'duplicate_facts': [],
177
+ 'contradicted_facts': [],
178
+ 'fact_type': 'DEFAULT',
179
+ }
180
+ )
181
+
182
+ clients = SimpleNamespace(
183
+ driver=MagicMock(),
184
+ llm_client=llm_client,
185
+ embedder=MagicMock(),
186
+ cross_encoder=MagicMock(),
187
+ ensure_ascii=True,
188
+ )
189
+
190
+ source_node = EntityNode(
191
+ uuid='source_uuid',
192
+ name='Document Node',
193
+ group_id='group_1',
194
+ labels=['Document'],
195
+ )
196
+ target_node = EntityNode(
197
+ uuid='target_uuid',
198
+ name='Topic Node',
199
+ group_id='group_1',
200
+ labels=['Topic'],
201
+ )
202
+
203
+ extracted_edge = EntityEdge(
204
+ source_node_uuid=source_node.uuid,
205
+ target_node_uuid=target_node.uuid,
206
+ name='OCCURRED_AT',
207
+ group_id='group_1',
208
+ fact='Document occurred at somewhere',
209
+ episodes=[],
210
+ created_at=datetime.now(timezone.utc),
211
+ valid_at=None,
212
+ invalid_at=None,
213
+ )
214
+
215
+ episode = EpisodicNode(
216
+ uuid='episode_uuid',
217
+ name='Episode',
218
+ group_id='group_1',
219
+ source='message',
220
+ source_description='desc',
221
+ content='Episode content',
222
+ valid_at=datetime.now(timezone.utc),
223
+ )
224
+
225
+ edge_types = {'OCCURRED_AT': OccurredAtEdge}
226
+ edge_type_map = {('Event', 'Entity'): ['OCCURRED_AT']}
227
+
228
+ resolved_edges, invalidated_edges = await resolve_extracted_edges(
229
+ clients,
230
+ [extracted_edge],
231
+ episode,
232
+ [source_node, target_node],
233
+ edge_types,
234
+ edge_type_map,
235
+ )
236
+
237
+ assert resolved_edges[0].name == DEFAULT_EDGE_NAME
238
+ assert invalidated_edges == []
239
+
240
+
241
+ @pytest.mark.asyncio
242
+ async def test_resolve_extracted_edges_keeps_unknown_names(monkeypatch):
243
+ from graphiti_core.utils.maintenance import edge_operations as edge_ops
244
+
245
+ monkeypatch.setattr(edge_ops, 'create_entity_edge_embeddings', AsyncMock(return_value=None))
246
+ monkeypatch.setattr(EntityEdge, 'get_between_nodes', AsyncMock(return_value=[]))
247
+
248
+ async def immediate_gather(*aws, max_coroutines=None):
249
+ return [await aw for aw in aws]
250
+
251
+ monkeypatch.setattr(edge_ops, 'semaphore_gather', immediate_gather)
252
+ monkeypatch.setattr(edge_ops, 'search', AsyncMock(return_value=SearchResults()))
253
+
254
+ llm_client = MagicMock()
255
+ llm_client.generate_response = AsyncMock(
256
+ return_value={
257
+ 'duplicate_facts': [],
258
+ 'contradicted_facts': [],
259
+ 'fact_type': 'DEFAULT',
260
+ }
261
+ )
262
+
263
+ clients = SimpleNamespace(
264
+ driver=MagicMock(),
265
+ llm_client=llm_client,
266
+ embedder=MagicMock(),
267
+ cross_encoder=MagicMock(),
268
+ ensure_ascii=True,
269
+ )
270
+
271
+ source_node = EntityNode(
272
+ uuid='source_uuid',
273
+ name='User Node',
274
+ group_id='group_1',
275
+ labels=['User'],
276
+ )
277
+ target_node = EntityNode(
278
+ uuid='target_uuid',
279
+ name='Topic Node',
280
+ group_id='group_1',
281
+ labels=['Topic'],
282
+ )
283
+
284
+ extracted_edge = EntityEdge(
285
+ source_node_uuid=source_node.uuid,
286
+ target_node_uuid=target_node.uuid,
287
+ name='INTERACTED_WITH',
288
+ group_id='group_1',
289
+ fact='User interacted with topic',
290
+ episodes=[],
291
+ created_at=datetime.now(timezone.utc),
292
+ valid_at=None,
293
+ invalid_at=None,
294
+ )
295
+
296
+ episode = EpisodicNode(
297
+ uuid='episode_uuid',
298
+ name='Episode',
299
+ group_id='group_1',
300
+ source='message',
301
+ source_description='desc',
302
+ content='Episode content',
303
+ valid_at=datetime.now(timezone.utc),
304
+ )
305
+
306
+ edge_types = {'OCCURRED_AT': OccurredAtEdge}
307
+ edge_type_map = {('Event', 'Entity'): ['OCCURRED_AT']}
308
+
309
+ resolved_edges, invalidated_edges = await resolve_extracted_edges(
310
+ clients,
311
+ [extracted_edge],
312
+ episode,
313
+ [source_node, target_node],
314
+ edge_types,
315
+ edge_type_map,
316
+ )
317
+
318
+ assert resolved_edges[0].name == 'INTERACTED_WITH'
319
+ assert invalidated_edges == []
320
+
321
+
322
+ @pytest.mark.asyncio
323
+ async def test_resolve_extracted_edge_rejects_unmapped_fact_type(mock_llm_client):
324
+ mock_llm_client.generate_response.return_value = {
325
+ 'duplicate_facts': [],
326
+ 'contradicted_facts': [],
327
+ 'fact_type': 'OCCURRED_AT',
328
+ }
329
+
330
+ extracted_edge = EntityEdge(
331
+ source_node_uuid='source_uuid',
332
+ target_node_uuid='target_uuid',
333
+ name='OCCURRED_AT',
334
+ group_id='group_1',
335
+ fact='Document occurred at somewhere',
336
+ episodes=[],
337
+ created_at=datetime.now(timezone.utc),
338
+ valid_at=None,
339
+ invalid_at=None,
340
+ )
341
+
342
+ episode = EpisodicNode(
343
+ uuid='episode_uuid',
344
+ name='Episode',
345
+ group_id='group_1',
346
+ source='message',
347
+ source_description='desc',
348
+ content='Episode content',
349
+ valid_at=datetime.now(timezone.utc),
350
+ )
351
+
352
+ related_edge = EntityEdge(
353
+ source_node_uuid='alt_source',
354
+ target_node_uuid='alt_target',
355
+ name='OTHER',
356
+ group_id='group_1',
357
+ fact='Different fact',
358
+ episodes=[],
359
+ created_at=datetime.now(timezone.utc),
360
+ valid_at=None,
361
+ invalid_at=None,
362
+ )
363
+
364
+ resolved_edge, duplicates, invalidated = await resolve_extracted_edge(
365
+ mock_llm_client,
366
+ extracted_edge,
367
+ [related_edge],
368
+ [],
369
+ episode,
370
+ edge_type_candidates={},
371
+ custom_edge_type_names={'OCCURRED_AT'},
372
+ ensure_ascii=True,
373
+ )
374
+
375
+ assert resolved_edge.name == DEFAULT_EDGE_NAME
376
+ assert duplicates == []
377
+ assert invalidated == []
378
+
379
+
380
+ @pytest.mark.asyncio
381
+ async def test_resolve_extracted_edge_accepts_unknown_fact_type(mock_llm_client):
382
+ mock_llm_client.generate_response.return_value = {
383
+ 'duplicate_facts': [],
384
+ 'contradicted_facts': [],
385
+ 'fact_type': 'INTERACTED_WITH',
386
+ }
387
+
388
+ extracted_edge = EntityEdge(
389
+ source_node_uuid='source_uuid',
390
+ target_node_uuid='target_uuid',
391
+ name='DEFAULT',
392
+ group_id='group_1',
393
+ fact='User interacted with topic',
394
+ episodes=[],
395
+ created_at=datetime.now(timezone.utc),
396
+ valid_at=None,
397
+ invalid_at=None,
398
+ )
399
+
400
+ episode = EpisodicNode(
401
+ uuid='episode_uuid',
402
+ name='Episode',
403
+ group_id='group_1',
404
+ source='message',
405
+ source_description='desc',
406
+ content='Episode content',
407
+ valid_at=datetime.now(timezone.utc),
408
+ )
409
+
410
+ related_edge = EntityEdge(
411
+ source_node_uuid='source_uuid',
412
+ target_node_uuid='target_uuid',
413
+ name='DEFAULT',
414
+ group_id='group_1',
415
+ fact='User mentioned a topic',
416
+ episodes=[],
417
+ created_at=datetime.now(timezone.utc),
418
+ valid_at=None,
419
+ invalid_at=None,
420
+ )
421
+
422
+ resolved_edge, duplicates, invalidated = await resolve_extracted_edge(
423
+ mock_llm_client,
424
+ extracted_edge,
425
+ [related_edge],
426
+ [],
427
+ episode,
428
+ edge_type_candidates={'OCCURRED_AT': OccurredAtEdge},
429
+ custom_edge_type_names={'OCCURRED_AT'},
430
+ ensure_ascii=True,
431
+ )
432
+
433
+ assert resolved_edge.name == 'INTERACTED_WITH'
434
+ assert resolved_edge.attributes == {}
435
+ assert duplicates == []
436
+ assert invalidated == []
@@ -783,7 +783,7 @@ wheels = [
783
783
 
784
784
  [[package]]
785
785
  name = "graphiti-core"
786
- version = "0.30.0rc3"
786
+ version = "0.30.0rc5"
787
787
  source = { editable = "." }
788
788
  dependencies = [
789
789
  { name = "diskcache" },
@@ -803,6 +803,7 @@ anthropic = [
803
803
  ]
804
804
  dev = [
805
805
  { name = "anthropic" },
806
+ { name = "boto3" },
806
807
  { name = "diskcache-stubs" },
807
808
  { name = "falkordb" },
808
809
  { name = "google-genai" },
@@ -811,9 +812,11 @@ dev = [
811
812
  { name = "jupyterlab" },
812
813
  { name = "kuzu" },
813
814
  { name = "langchain-anthropic" },
815
+ { name = "langchain-aws" },
814
816
  { name = "langchain-openai" },
815
817
  { name = "langgraph" },
816
818
  { name = "langsmith" },
819
+ { name = "opensearch-py" },
817
820
  { name = "pyright" },
818
821
  { name = "pytest" },
819
822
  { name = "pytest-asyncio" },
@@ -855,6 +858,7 @@ voyageai = [
855
858
  requires-dist = [
856
859
  { name = "anthropic", marker = "extra == 'anthropic'", specifier = ">=0.49.0" },
857
860
  { name = "anthropic", marker = "extra == 'dev'", specifier = ">=0.49.0" },
861
+ { name = "boto3", marker = "extra == 'dev'", specifier = ">=1.39.16" },
858
862
  { name = "boto3", marker = "extra == 'neo4j-opensearch'", specifier = ">=1.39.16" },
859
863
  { name = "boto3", marker = "extra == 'neptune'", specifier = ">=1.39.16" },
860
864
  { name = "diskcache", specifier = ">=5.6.3" },
@@ -870,6 +874,7 @@ requires-dist = [
870
874
  { name = "kuzu", marker = "extra == 'dev'", specifier = ">=0.11.2" },
871
875
  { name = "kuzu", marker = "extra == 'kuzu'", specifier = ">=0.11.2" },
872
876
  { name = "langchain-anthropic", marker = "extra == 'dev'", specifier = ">=0.2.4" },
877
+ { name = "langchain-aws", marker = "extra == 'dev'", specifier = ">=0.2.29" },
873
878
  { name = "langchain-aws", marker = "extra == 'neptune'", specifier = ">=0.2.29" },
874
879
  { name = "langchain-openai", marker = "extra == 'dev'", specifier = ">=0.2.6" },
875
880
  { name = "langgraph", marker = "extra == 'dev'", specifier = ">=0.2.15" },
@@ -877,6 +882,7 @@ requires-dist = [
877
882
  { name = "neo4j", specifier = ">=5.26.0" },
878
883
  { name = "numpy", specifier = ">=1.0.0" },
879
884
  { name = "openai", specifier = ">=1.91.0" },
885
+ { name = "opensearch-py", marker = "extra == 'dev'", specifier = ">=3.0.0" },
880
886
  { name = "opensearch-py", marker = "extra == 'neo4j-opensearch'", specifier = ">=3.0.0" },
881
887
  { name = "opensearch-py", marker = "extra == 'neptune'", specifier = ">=3.0.0" },
882
888
  { name = "posthog", specifier = ">=3.0.0" },