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.
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/PKG-INFO +4 -1
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/graphiti.py +1 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/bulk_utils.py +1 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/edge_operations.py +75 -6
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/pyproject.toml +4 -1
- graphiti_core-0.30.0rc5/tests/utils/maintenance/test_edge_operations.py +436 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/uv.lock +7 -1
- graphiti_core-0.30.0rc3/tests/utils/maintenance/test_edge_operations.py +0 -144
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.env.example +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/dependabot.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/pull_request_template.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/secret_scanning.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/ai-moderator.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/cla.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/claude-code-review.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/claude.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/codeql.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/lint.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/mcp-server-docker.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/release-graphiti-core.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/typecheck.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.github/workflows/unit_tests.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/.gitignore +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/AGENTS.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/CLAUDE.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/CODE_OF_CONDUCT.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/CONTRIBUTING.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/Dockerfile +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/LICENSE +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/Makefile +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/README.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/SECURITY.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/Zep-CLA.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/conftest.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/depot.json +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/docker-compose.test.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/docker-compose.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/ellipsis.yaml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/data/manybirds_products.json +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/ecommerce/runner.ipynb +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/ecommerce/runner.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/langgraph-agent/agent.ipynb +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/langgraph-agent/tinybirds-jess.png +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/podcast/podcast_runner.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/podcast/podcast_transcript.txt +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/podcast/transcript_parser.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/README.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/quickstart_falkordb.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/quickstart_neo4j.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/quickstart_neptune.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/quickstart/requirements.txt +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/wizard_of_oz/parser.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/wizard_of_oz/runner.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/examples/wizard_of_oz/woo.txt +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/bge_reranker_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/gemini_reranker_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/cross_encoder/openai_reranker_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/driver.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/falkordb_driver.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/kuzu_driver.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/neo4j_driver.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/driver/neptune_driver.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/edges.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/azure_openai.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/gemini.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/openai.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/embedder/voyage.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/errors.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/graph_queries.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/graphiti_types.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/helpers.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/anthropic_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/azure_openai_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/config.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/errors.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/gemini_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/groq_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/openai_base_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/openai_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/openai_generic_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/llm_client/utils.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/migrations/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/edges/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/edges/edge_db_queries.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/nodes/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/models/nodes/node_db_queries.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/nodes.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/dedupe_edges.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/dedupe_nodes.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/eval.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/extract_edge_dates.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/extract_edges.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/extract_nodes.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/invalidate_edges.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/lib.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/models.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/prompt_helpers.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/prompts/summarize_nodes.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/py.typed +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_config.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_config_recipes.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_filters.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_helpers.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/search/search_utils.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/telemetry/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/telemetry/telemetry.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/datetime_utils.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/community_operations.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/dedup_helpers.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/graph_data_operations.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/node_operations.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/temporal_operations.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/maintenance/utils.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/graphiti_core/utils/ontology_utils/entity_types_utils.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/images/arxiv-screenshot.png +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/images/graphiti-graph-intro.gif +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/images/graphiti-intro-slides-stock-2.gif +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/images/simple_graph.svg +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/.env.example +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/.python-version +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/Dockerfile +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/README.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/cursor_rules.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/docker-compose.yml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/graphiti_mcp_server.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/mcp_config_sse_example.json +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/mcp_config_stdio_example.json +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/pyproject.toml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/mcp_server/uv.lock +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/py.typed +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/pytest.ini +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/.env.example +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/Makefile +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/README.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/config.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/dto/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/dto/common.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/dto/ingest.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/dto/retrieve.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/main.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/routers/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/routers/ingest.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/routers/retrieve.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/graph_service/zep_graphiti.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/pyproject.toml +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/server/uv.lock +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/signatures/version1/cla.json +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/cross_encoder/test_bge_reranker_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/cross_encoder/test_gemini_reranker_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/driver/__init__.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/driver/test_falkordb_driver.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/embedder/embedder_fixtures.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/embedder/test_gemini.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/embedder/test_openai.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/embedder/test_voyage.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/data/longmemeval_data/README.md +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/data/longmemeval_data/longmemeval_oracle.json +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/eval_cli.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/eval_e2e_graph_building.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/pytest.ini +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/evals/utils.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/helpers_test.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_anthropic_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_anthropic_client_int.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_errors.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/llm_client/test_gemini_client.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_edge_int.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_entity_exclusion_int.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_graphiti_int.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_graphiti_mock.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/test_node_int.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/utils/maintenance/test_bulk_utils.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/utils/maintenance/test_node_operations.py +0 -0
- {graphiti_core-0.30.0rc3 → graphiti_core-0.30.0rc5}/tests/utils/maintenance/test_temporal_operations_int.py +0 -0
- {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.
|
|
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'
|
|
@@ -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
|
-
|
|
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(
|
|
482
|
+
for i, (type_name, type_model) in enumerate(edge_type_candidates.items())
|
|
433
483
|
]
|
|
434
|
-
if
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
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" },
|