graphiti-core 0.21.0rc9__tar.gz → 0.21.0rc11__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.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/claude-code-review.yml +3 -0
- graphiti_core-0.21.0rc11/.github/workflows/daily_issue_maintenance.yml +123 -0
- graphiti_core-0.21.0rc11/.github/workflows/issue-triage.yml +140 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/PKG-INFO +1 -1
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/edges.py +6 -3
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/nodes.py +6 -3
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/dedupe_nodes.py +2 -1
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/extract_nodes.py +1 -1
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/maintenance/node_operations.py +65 -9
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/pyproject.toml +1 -1
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/utils/maintenance/test_node_operations.py +182 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/uv.lock +2 -2
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.env.example +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/dependabot.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/pull_request_template.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/secret_scanning.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/ai-moderator.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/cla.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/claude.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/codeql.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/lint.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/mcp-server-docker.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/release-graphiti-core.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/typecheck.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/unit_tests.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.gitignore +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/AGENTS.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/CLAUDE.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/CODE_OF_CONDUCT.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/CONTRIBUTING.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/Dockerfile +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/LICENSE +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/Makefile +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/README.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/SECURITY.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/Zep-CLA.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/conftest.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/depot.json +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/docker-compose.test.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/docker-compose.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/ellipsis.yaml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/data/manybirds_products.json +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/ecommerce/runner.ipynb +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/ecommerce/runner.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/langgraph-agent/agent.ipynb +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/langgraph-agent/tinybirds-jess.png +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/podcast/podcast_runner.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/podcast/podcast_transcript.txt +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/podcast/transcript_parser.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/quickstart/README.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/quickstart/quickstart_falkordb.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/quickstart/quickstart_neo4j.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/quickstart/quickstart_neptune.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/quickstart/requirements.txt +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/wizard_of_oz/parser.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/wizard_of_oz/runner.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/examples/wizard_of_oz/woo.txt +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/cross_encoder/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/cross_encoder/bge_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/cross_encoder/client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/cross_encoder/gemini_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/cross_encoder/openai_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/driver/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/driver/driver.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/driver/falkordb_driver.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/driver/kuzu_driver.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/driver/neo4j_driver.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/driver/neptune_driver.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/embedder/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/embedder/azure_openai.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/embedder/client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/embedder/gemini.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/embedder/openai.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/embedder/voyage.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/errors.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/graph_queries.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/graphiti.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/graphiti_types.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/helpers.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/anthropic_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/azure_openai_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/config.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/errors.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/gemini_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/groq_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/openai_base_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/openai_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/openai_generic_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/llm_client/utils.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/migrations/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/models/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/models/edges/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/models/edges/edge_db_queries.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/models/nodes/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/models/nodes/node_db_queries.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/dedupe_edges.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/eval.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/extract_edge_dates.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/extract_edges.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/invalidate_edges.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/lib.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/models.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/prompt_helpers.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/prompts/summarize_nodes.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/py.typed +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/search/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/search/search.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/search/search_config.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/search/search_config_recipes.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/search/search_filters.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/search/search_helpers.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/search/search_utils.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/telemetry/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/telemetry/telemetry.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/bulk_utils.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/datetime_utils.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/maintenance/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/maintenance/community_operations.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/maintenance/dedup_helpers.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/maintenance/edge_operations.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/maintenance/graph_data_operations.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/maintenance/temporal_operations.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/maintenance/utils.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/graphiti_core/utils/ontology_utils/entity_types_utils.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/images/arxiv-screenshot.png +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/images/graphiti-graph-intro.gif +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/images/graphiti-intro-slides-stock-2.gif +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/images/simple_graph.svg +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/.env.example +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/.python-version +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/Dockerfile +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/README.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/cursor_rules.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/docker-compose.yml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/graphiti_mcp_server.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/mcp_config_sse_example.json +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/mcp_config_stdio_example.json +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/pyproject.toml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/mcp_server/uv.lock +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/py.typed +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/pytest.ini +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/.env.example +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/Makefile +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/README.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/config.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/dto/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/dto/common.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/dto/ingest.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/dto/retrieve.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/main.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/routers/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/routers/ingest.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/routers/retrieve.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/graph_service/zep_graphiti.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/pyproject.toml +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/server/uv.lock +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/signatures/version1/cla.json +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/cross_encoder/test_bge_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/cross_encoder/test_gemini_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/driver/__init__.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/driver/test_falkordb_driver.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/embedder/embedder_fixtures.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/embedder/test_gemini.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/embedder/test_openai.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/embedder/test_voyage.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/evals/data/longmemeval_data/README.md +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/evals/data/longmemeval_data/longmemeval_oracle.json +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/evals/eval_cli.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/evals/eval_e2e_graph_building.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/evals/pytest.ini +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/evals/utils.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/helpers_test.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/llm_client/test_anthropic_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/llm_client/test_anthropic_client_int.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/llm_client/test_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/llm_client/test_errors.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/llm_client/test_gemini_client.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/test_edge_int.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/test_entity_exclusion_int.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/test_graphiti_int.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/test_graphiti_mock.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/test_node_int.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/utils/maintenance/test_bulk_utils.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/utils/maintenance/test_edge_operations.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/utils/maintenance/test_temporal_operations_int.py +0 -0
- {graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/utils/search/search_utils_test.py +0 -0
{graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/.github/workflows/claude-code-review.yml
RENAMED
|
@@ -29,6 +29,8 @@ jobs:
|
|
|
29
29
|
|
|
30
30
|
Please review this pull request.
|
|
31
31
|
|
|
32
|
+
IMPORTANT: Your role is to critically review code. You must not provide POSITIVE feedback on code, this only adds noise to the review process.
|
|
33
|
+
|
|
32
34
|
Note: The PR branch is already checked out in the current working directory.
|
|
33
35
|
|
|
34
36
|
Focus on:
|
|
@@ -48,3 +50,4 @@ jobs:
|
|
|
48
50
|
|
|
49
51
|
claude_args: |
|
|
50
52
|
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*)"
|
|
53
|
+
--model claude-sonnet-4-5-20250929
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
name: Daily Issue Maintenance
|
|
2
|
+
on:
|
|
3
|
+
schedule:
|
|
4
|
+
- cron: "0 0 * * *" # Every day at midnight
|
|
5
|
+
workflow_dispatch: # Manual trigger option
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
find-legacy-duplicates:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
if: github.event_name == 'workflow_dispatch'
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
issues: write
|
|
14
|
+
id-token: write
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
with:
|
|
18
|
+
fetch-depth: 1
|
|
19
|
+
|
|
20
|
+
- uses: anthropics/claude-code-action@v1
|
|
21
|
+
with:
|
|
22
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
23
|
+
prompt: |
|
|
24
|
+
REPO: ${{ github.repository }}
|
|
25
|
+
|
|
26
|
+
Find potential duplicate issues in the repository:
|
|
27
|
+
|
|
28
|
+
1. Use `gh issue list --state open --limit 1000 --json number,title,body,createdAt` to get all open issues
|
|
29
|
+
2. For each issue, search for potential duplicates using `gh search issues` with keywords from the title and body
|
|
30
|
+
3. Compare issues to identify true duplicates using these criteria:
|
|
31
|
+
- Same bug or error being reported
|
|
32
|
+
- Same feature request (even if worded differently)
|
|
33
|
+
- Same question being asked
|
|
34
|
+
- Issues describing the same root problem
|
|
35
|
+
|
|
36
|
+
For each duplicate found:
|
|
37
|
+
- Add a comment linking to the original issue
|
|
38
|
+
- Apply the "duplicate" label using `gh issue edit`
|
|
39
|
+
- Be polite and explain why it's a duplicate
|
|
40
|
+
|
|
41
|
+
Focus on finding true duplicates, not just similar issues.
|
|
42
|
+
|
|
43
|
+
claude_args: |
|
|
44
|
+
--allowedTools "Bash(gh issue:*),Bash(gh search:*)"
|
|
45
|
+
--model claude-sonnet-4-5-20250929
|
|
46
|
+
|
|
47
|
+
check-stale-issues:
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
if: github.event_name == 'schedule'
|
|
50
|
+
permissions:
|
|
51
|
+
contents: read
|
|
52
|
+
issues: write
|
|
53
|
+
id-token: write
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@v4
|
|
56
|
+
with:
|
|
57
|
+
fetch-depth: 1
|
|
58
|
+
|
|
59
|
+
- uses: anthropics/claude-code-action@v1
|
|
60
|
+
with:
|
|
61
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
62
|
+
prompt: |
|
|
63
|
+
REPO: ${{ github.repository }}
|
|
64
|
+
|
|
65
|
+
Review stale issues and request confirmation:
|
|
66
|
+
|
|
67
|
+
1. Use `gh issue list --state open --limit 1000 --json number,title,updatedAt,comments` to get all open issues
|
|
68
|
+
2. Identify issues that are:
|
|
69
|
+
- Older than 60 days (based on updatedAt)
|
|
70
|
+
- Have no comments with "stale-check" label
|
|
71
|
+
- Are not labeled as "enhancement" or "documentation"
|
|
72
|
+
3. For each stale issue:
|
|
73
|
+
- Add a polite comment asking the issue originator if this is still relevant
|
|
74
|
+
- Apply a "stale-check" label to track that we've asked
|
|
75
|
+
- Use format: "@{author} Is this still an issue? Please confirm within 14 days or this issue will be closed."
|
|
76
|
+
|
|
77
|
+
Use:
|
|
78
|
+
- `gh issue view` to check issue details and labels
|
|
79
|
+
- `gh issue comment` to add comments
|
|
80
|
+
- `gh issue edit` to add the "stale-check" label
|
|
81
|
+
|
|
82
|
+
claude_args: |
|
|
83
|
+
--allowedTools "Bash(gh issue:*)"
|
|
84
|
+
--model claude-sonnet-4-5-20250929
|
|
85
|
+
|
|
86
|
+
close-unconfirmed-issues:
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
if: github.event_name == 'schedule'
|
|
89
|
+
needs: check-stale-issues
|
|
90
|
+
permissions:
|
|
91
|
+
contents: read
|
|
92
|
+
issues: write
|
|
93
|
+
id-token: write
|
|
94
|
+
steps:
|
|
95
|
+
- uses: actions/checkout@v4
|
|
96
|
+
with:
|
|
97
|
+
fetch-depth: 1
|
|
98
|
+
|
|
99
|
+
- uses: anthropics/claude-code-action@v1
|
|
100
|
+
with:
|
|
101
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
102
|
+
prompt: |
|
|
103
|
+
REPO: ${{ github.repository }}
|
|
104
|
+
|
|
105
|
+
Close unconfirmed stale issues:
|
|
106
|
+
|
|
107
|
+
1. Use `gh issue list --state open --label "stale-check" --limit 1000 --json number,title,comments,updatedAt` to get issues with stale-check label
|
|
108
|
+
2. For each issue, check if:
|
|
109
|
+
- The "stale-check" comment was added 14+ days ago
|
|
110
|
+
- There has been no response from the issue author or activity since the comment
|
|
111
|
+
3. For issues meeting the criteria:
|
|
112
|
+
- Add a polite closing comment
|
|
113
|
+
- Close the issue using `gh issue close`
|
|
114
|
+
- Use format: "Closing due to inactivity. Feel free to reopen if this is still relevant."
|
|
115
|
+
|
|
116
|
+
Use:
|
|
117
|
+
- `gh issue view` to check issue comments and activity
|
|
118
|
+
- `gh issue comment` to add closing comment
|
|
119
|
+
- `gh issue close` to close the issue
|
|
120
|
+
|
|
121
|
+
claude_args: |
|
|
122
|
+
--allowedTools "Bash(gh issue:*)"
|
|
123
|
+
--model claude-sonnet-4-5-20250929
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
name: Issue Triage and Deduplication
|
|
2
|
+
on:
|
|
3
|
+
issues:
|
|
4
|
+
types: [opened]
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
triage:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
timeout-minutes: 10
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
issues: write
|
|
13
|
+
id-token: write
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout repository
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 1
|
|
20
|
+
|
|
21
|
+
- name: Run Claude Code for Issue Triage
|
|
22
|
+
uses: anthropics/claude-code-action@v1
|
|
23
|
+
with:
|
|
24
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
25
|
+
allowed_non_write_users: "*"
|
|
26
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
27
|
+
prompt: |
|
|
28
|
+
You're an issue triage assistant for GitHub issues. Your task is to analyze the issue and select appropriate labels from the provided list.
|
|
29
|
+
|
|
30
|
+
IMPORTANT: Don't post any comments or messages to the issue. Your only action should be to apply labels. DO NOT check for duplicates - that's handled by a separate job.
|
|
31
|
+
|
|
32
|
+
Issue Information:
|
|
33
|
+
- REPO: ${{ github.repository }}
|
|
34
|
+
- ISSUE_NUMBER: ${{ github.event.issue.number }}
|
|
35
|
+
|
|
36
|
+
TASK OVERVIEW:
|
|
37
|
+
|
|
38
|
+
1. First, fetch the list of labels available in this repository by running: `gh label list`. Run exactly this command with nothing else.
|
|
39
|
+
|
|
40
|
+
2. Next, use gh commands to get context about the issue:
|
|
41
|
+
- Use `gh issue view ${{ github.event.issue.number }}` to retrieve the current issue's details
|
|
42
|
+
- Use `gh search issues` to find similar issues that might provide context for proper categorization
|
|
43
|
+
- You have access to these Bash commands:
|
|
44
|
+
- Bash(gh label list:*) - to get available labels
|
|
45
|
+
- Bash(gh issue view:*) - to view issue details
|
|
46
|
+
- Bash(gh issue edit:*) - to apply labels to the issue
|
|
47
|
+
- Bash(gh search:*) - to search for similar issues
|
|
48
|
+
|
|
49
|
+
3. Analyze the issue content, considering:
|
|
50
|
+
- The issue title and description
|
|
51
|
+
- The type of issue (bug report, feature request, question, etc.)
|
|
52
|
+
- Technical areas mentioned
|
|
53
|
+
- Database mentions (neo4j, falkordb, neptune, etc.)
|
|
54
|
+
- LLM providers mentioned (openai, anthropic, gemini, groq, etc.)
|
|
55
|
+
- Components affected (embeddings, search, prompts, server, mcp, etc.)
|
|
56
|
+
|
|
57
|
+
4. Select appropriate labels from the available labels list:
|
|
58
|
+
- Choose labels that accurately reflect the issue's nature
|
|
59
|
+
- Be specific but comprehensive
|
|
60
|
+
- Add database-specific labels if mentioned: neo4j, falkordb, neptune
|
|
61
|
+
- Add component labels if applicable
|
|
62
|
+
- DO NOT add priority labels (P1, P2, P3)
|
|
63
|
+
- DO NOT add duplicate label - that's handled by the deduplication job
|
|
64
|
+
|
|
65
|
+
5. Apply the selected labels:
|
|
66
|
+
- Use `gh issue edit ${{ github.event.issue.number }} --add-label "label1,label2,label3"` to apply your selected labels
|
|
67
|
+
- DO NOT post any comments explaining your decision
|
|
68
|
+
- DO NOT communicate directly with users
|
|
69
|
+
- If no labels are clearly applicable, do not apply any labels
|
|
70
|
+
|
|
71
|
+
IMPORTANT GUIDELINES:
|
|
72
|
+
- Be thorough in your analysis
|
|
73
|
+
- Only select labels from the provided list
|
|
74
|
+
- DO NOT post any comments to the issue
|
|
75
|
+
- Your ONLY action should be to apply labels using gh issue edit
|
|
76
|
+
- It's okay to not add any labels if none are clearly applicable
|
|
77
|
+
- DO NOT check for duplicates
|
|
78
|
+
|
|
79
|
+
claude_args: |
|
|
80
|
+
--allowedTools "Bash(gh label list:*),Bash(gh issue view:*),Bash(gh issue edit:*),Bash(gh search:*)"
|
|
81
|
+
--model claude-sonnet-4-5-20250929
|
|
82
|
+
|
|
83
|
+
deduplicate:
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
timeout-minutes: 10
|
|
86
|
+
needs: triage
|
|
87
|
+
permissions:
|
|
88
|
+
contents: read
|
|
89
|
+
issues: write
|
|
90
|
+
id-token: write
|
|
91
|
+
|
|
92
|
+
steps:
|
|
93
|
+
- name: Checkout repository
|
|
94
|
+
uses: actions/checkout@v4
|
|
95
|
+
with:
|
|
96
|
+
fetch-depth: 1
|
|
97
|
+
|
|
98
|
+
- name: Check for duplicate issues
|
|
99
|
+
uses: anthropics/claude-code-action@v1
|
|
100
|
+
with:
|
|
101
|
+
prompt: |
|
|
102
|
+
Analyze this new issue and check if it's a duplicate of existing issues in the repository.
|
|
103
|
+
|
|
104
|
+
Issue: #${{ github.event.issue.number }}
|
|
105
|
+
Repository: ${{ github.repository }}
|
|
106
|
+
|
|
107
|
+
Your task:
|
|
108
|
+
1. Use mcp__github__get_issue to get details of the current issue (#${{ github.event.issue.number }})
|
|
109
|
+
2. Search for similar existing OPEN issues using mcp__github__search_issues with relevant keywords from the issue title and body
|
|
110
|
+
3. Compare the new issue with existing ones to identify potential duplicates
|
|
111
|
+
|
|
112
|
+
Criteria for duplicates:
|
|
113
|
+
- Same bug or error being reported
|
|
114
|
+
- Same feature request (even if worded differently)
|
|
115
|
+
- Same question being asked
|
|
116
|
+
- Issues describing the same root problem
|
|
117
|
+
|
|
118
|
+
If you find duplicates:
|
|
119
|
+
- Add a comment on the new issue linking to the original issue(s)
|
|
120
|
+
- Apply the "duplicate" label to the new issue
|
|
121
|
+
- Be polite and explain why it's a duplicate
|
|
122
|
+
- Suggest the user follow the original issue for updates
|
|
123
|
+
|
|
124
|
+
If it's NOT a duplicate:
|
|
125
|
+
- Don't add any comments
|
|
126
|
+
- Don't modify labels
|
|
127
|
+
|
|
128
|
+
Use these tools:
|
|
129
|
+
- mcp__github__get_issue: Get issue details
|
|
130
|
+
- mcp__github__search_issues: Search for similar issues (use state:open)
|
|
131
|
+
- mcp__github__list_issues: List recent issues if needed
|
|
132
|
+
- mcp__github__create_issue_comment: Add a comment if duplicate found
|
|
133
|
+
- mcp__github__update_issue: Add "duplicate" label
|
|
134
|
+
|
|
135
|
+
Be thorough but efficient. Focus on finding true duplicates, not just similar issues.
|
|
136
|
+
|
|
137
|
+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
138
|
+
claude_args: |
|
|
139
|
+
--allowedTools "mcp__github__get_issue,mcp__github__search_issues,mcp__github__list_issues,mcp__github__create_issue_comment,mcp__github__update_issue,mcp__github__get_issue_comments"
|
|
140
|
+
--model claude-sonnet-4-5-20250929
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphiti-core
|
|
3
|
-
Version: 0.21.
|
|
3
|
+
Version: 0.21.0rc11
|
|
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
|
|
@@ -644,8 +644,11 @@ def get_community_edge_from_record(record: Any):
|
|
|
644
644
|
|
|
645
645
|
|
|
646
646
|
async def create_entity_edge_embeddings(embedder: EmbedderClient, edges: list[EntityEdge]):
|
|
647
|
-
|
|
647
|
+
# filter out falsey values from edges
|
|
648
|
+
filtered_edges = [edge for edge in edges if edge.fact]
|
|
649
|
+
|
|
650
|
+
if len(filtered_edges) == 0:
|
|
648
651
|
return
|
|
649
|
-
fact_embeddings = await embedder.create_batch([edge.fact for edge in
|
|
650
|
-
for edge, fact_embedding in zip(
|
|
652
|
+
fact_embeddings = await embedder.create_batch([edge.fact for edge in filtered_edges])
|
|
653
|
+
for edge, fact_embedding in zip(filtered_edges, fact_embeddings, strict=True):
|
|
651
654
|
edge.fact_embedding = fact_embedding
|
|
@@ -868,9 +868,12 @@ def get_community_node_from_record(record: Any) -> CommunityNode:
|
|
|
868
868
|
|
|
869
869
|
|
|
870
870
|
async def create_entity_node_embeddings(embedder: EmbedderClient, nodes: list[EntityNode]):
|
|
871
|
-
|
|
871
|
+
# filter out falsey values from nodes
|
|
872
|
+
filtered_nodes = [node for node in nodes if node.name]
|
|
873
|
+
|
|
874
|
+
if not filtered_nodes:
|
|
872
875
|
return
|
|
873
876
|
|
|
874
|
-
name_embeddings = await embedder.create_batch([node.name for node in
|
|
875
|
-
for node, name_embedding in zip(
|
|
877
|
+
name_embeddings = await embedder.create_batch([node.name for node in filtered_nodes])
|
|
878
|
+
for node, name_embedding in zip(filtered_nodes, name_embeddings, strict=True):
|
|
876
879
|
node.name_embedding = name_embedding
|
|
@@ -166,7 +166,8 @@ def nodes(context: dict[str, Any]) -> list[Message]:
|
|
|
166
166
|
- They have similar names or purposes but refer to separate instances or concepts.
|
|
167
167
|
|
|
168
168
|
Task:
|
|
169
|
-
|
|
169
|
+
ENTITIES contains {len(context['extracted_nodes'])} entities with IDs 0 through {len(context['extracted_nodes']) - 1}.
|
|
170
|
+
Your response MUST include EXACTLY {len(context['extracted_nodes'])} resolutions with IDs 0 through {len(context['extracted_nodes']) - 1}. Do not skip or add IDs.
|
|
170
171
|
|
|
171
172
|
For every entity, return an object with the following keys:
|
|
172
173
|
{{
|
|
@@ -151,7 +151,7 @@ For each entity extracted, also determine its entity type based on the provided
|
|
|
151
151
|
Indicate the classified entity type by providing its entity_type_id.
|
|
152
152
|
|
|
153
153
|
Guidelines:
|
|
154
|
-
1.
|
|
154
|
+
1. Extract all entities that the JSON represents. This will often be something like a "name" or "user" field
|
|
155
155
|
2. Extract all entities mentioned in all other properties throughout the JSON structure
|
|
156
156
|
3. Do NOT extract any properties that contain dates
|
|
157
157
|
"""
|
|
@@ -15,6 +15,7 @@ limitations under the License.
|
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
import logging
|
|
18
|
+
from collections.abc import Awaitable, Callable
|
|
18
19
|
from time import time
|
|
19
20
|
from typing import Any
|
|
20
21
|
|
|
@@ -55,6 +56,8 @@ from graphiti_core.utils.maintenance.edge_operations import (
|
|
|
55
56
|
|
|
56
57
|
logger = logging.getLogger(__name__)
|
|
57
58
|
|
|
59
|
+
NodeSummaryFilter = Callable[[EntityNode], Awaitable[bool]]
|
|
60
|
+
|
|
58
61
|
|
|
59
62
|
async def extract_nodes_reflexion(
|
|
60
63
|
llm_client: LLMClient,
|
|
@@ -266,6 +269,27 @@ async def _resolve_with_llm(
|
|
|
266
269
|
for i, node in enumerate(llm_extracted_nodes)
|
|
267
270
|
]
|
|
268
271
|
|
|
272
|
+
sent_ids = [ctx['id'] for ctx in extracted_nodes_context]
|
|
273
|
+
logger.debug(
|
|
274
|
+
'Sending %d entities to LLM for deduplication with IDs 0-%d (actual IDs sent: %s)',
|
|
275
|
+
len(llm_extracted_nodes),
|
|
276
|
+
len(llm_extracted_nodes) - 1,
|
|
277
|
+
sent_ids if len(sent_ids) < 20 else f'{sent_ids[:10]}...{sent_ids[-10:]}',
|
|
278
|
+
)
|
|
279
|
+
if llm_extracted_nodes:
|
|
280
|
+
sample_size = min(3, len(extracted_nodes_context))
|
|
281
|
+
logger.debug(
|
|
282
|
+
'First %d entities: %s',
|
|
283
|
+
sample_size,
|
|
284
|
+
[(ctx['id'], ctx['name']) for ctx in extracted_nodes_context[:sample_size]],
|
|
285
|
+
)
|
|
286
|
+
if len(extracted_nodes_context) > 3:
|
|
287
|
+
logger.debug(
|
|
288
|
+
'Last %d entities: %s',
|
|
289
|
+
sample_size,
|
|
290
|
+
[(ctx['id'], ctx['name']) for ctx in extracted_nodes_context[-sample_size:]],
|
|
291
|
+
)
|
|
292
|
+
|
|
269
293
|
existing_nodes_context = [
|
|
270
294
|
{
|
|
271
295
|
**{
|
|
@@ -298,15 +322,38 @@ async def _resolve_with_llm(
|
|
|
298
322
|
valid_relative_range = range(len(state.unresolved_indices))
|
|
299
323
|
processed_relative_ids: set[int] = set()
|
|
300
324
|
|
|
325
|
+
received_ids = {r.id for r in node_resolutions}
|
|
326
|
+
expected_ids = set(valid_relative_range)
|
|
327
|
+
missing_ids = expected_ids - received_ids
|
|
328
|
+
extra_ids = received_ids - expected_ids
|
|
329
|
+
|
|
330
|
+
logger.debug(
|
|
331
|
+
'Received %d resolutions for %d entities',
|
|
332
|
+
len(node_resolutions),
|
|
333
|
+
len(state.unresolved_indices),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
if missing_ids:
|
|
337
|
+
logger.warning('LLM did not return resolutions for IDs: %s', sorted(missing_ids))
|
|
338
|
+
|
|
339
|
+
if extra_ids:
|
|
340
|
+
logger.warning(
|
|
341
|
+
'LLM returned invalid IDs outside valid range 0-%d: %s (all returned IDs: %s)',
|
|
342
|
+
len(state.unresolved_indices) - 1,
|
|
343
|
+
sorted(extra_ids),
|
|
344
|
+
sorted(received_ids),
|
|
345
|
+
)
|
|
346
|
+
|
|
301
347
|
for resolution in node_resolutions:
|
|
302
348
|
relative_id: int = resolution.id
|
|
303
349
|
duplicate_idx: int = resolution.duplicate_idx
|
|
304
350
|
|
|
305
351
|
if relative_id not in valid_relative_range:
|
|
306
352
|
logger.warning(
|
|
307
|
-
'Skipping invalid LLM dedupe id %
|
|
353
|
+
'Skipping invalid LLM dedupe id %d (valid range: 0-%d, received %d resolutions)',
|
|
308
354
|
relative_id,
|
|
309
|
-
state.unresolved_indices,
|
|
355
|
+
len(state.unresolved_indices) - 1,
|
|
356
|
+
len(node_resolutions),
|
|
310
357
|
)
|
|
311
358
|
continue
|
|
312
359
|
|
|
@@ -402,6 +449,7 @@ async def extract_attributes_from_nodes(
|
|
|
402
449
|
episode: EpisodicNode | None = None,
|
|
403
450
|
previous_episodes: list[EpisodicNode] | None = None,
|
|
404
451
|
entity_types: dict[str, type[BaseModel]] | None = None,
|
|
452
|
+
should_summarize_node: NodeSummaryFilter | None = None,
|
|
405
453
|
) -> list[EntityNode]:
|
|
406
454
|
llm_client = clients.llm_client
|
|
407
455
|
embedder = clients.embedder
|
|
@@ -418,6 +466,7 @@ async def extract_attributes_from_nodes(
|
|
|
418
466
|
else None
|
|
419
467
|
),
|
|
420
468
|
clients.ensure_ascii,
|
|
469
|
+
should_summarize_node,
|
|
421
470
|
)
|
|
422
471
|
for node in nodes
|
|
423
472
|
]
|
|
@@ -435,6 +484,7 @@ async def extract_attributes_from_node(
|
|
|
435
484
|
previous_episodes: list[EpisodicNode] | None = None,
|
|
436
485
|
entity_type: type[BaseModel] | None = None,
|
|
437
486
|
ensure_ascii: bool = False,
|
|
487
|
+
should_summarize_node: NodeSummaryFilter | None = None,
|
|
438
488
|
) -> EntityNode:
|
|
439
489
|
node_context: dict[str, Any] = {
|
|
440
490
|
'name': node.name,
|
|
@@ -477,16 +527,22 @@ async def extract_attributes_from_node(
|
|
|
477
527
|
else {}
|
|
478
528
|
)
|
|
479
529
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
530
|
+
# Determine if summary should be generated
|
|
531
|
+
generate_summary = True
|
|
532
|
+
if should_summarize_node is not None:
|
|
533
|
+
generate_summary = await should_summarize_node(node)
|
|
534
|
+
|
|
535
|
+
# Conditionally generate summary
|
|
536
|
+
if generate_summary:
|
|
537
|
+
summary_response = await llm_client.generate_response(
|
|
538
|
+
prompt_library.extract_nodes.extract_summary(summary_context),
|
|
539
|
+
response_model=EntitySummary,
|
|
540
|
+
model_size=ModelSize.small,
|
|
541
|
+
)
|
|
542
|
+
node.summary = summary_response.get('summary', '')
|
|
485
543
|
|
|
486
544
|
if has_entity_attributes and entity_type is not None:
|
|
487
545
|
entity_type(**llm_response)
|
|
488
|
-
|
|
489
|
-
node.summary = summary_response.get('summary', '')
|
|
490
546
|
node_attributes = {key: value for key, value in llm_response.items()}
|
|
491
547
|
|
|
492
548
|
node.attributes.update(node_attributes)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "graphiti-core"
|
|
3
3
|
description = "A temporal graph building library"
|
|
4
|
-
version = "0.21.
|
|
4
|
+
version = "0.21.0pre11"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Paul Paliychuk", email = "paul@getzep.com" },
|
|
7
7
|
{ name = "Preston Rasmussen", email = "preston@getzep.com" },
|
{graphiti_core-0.21.0rc9 → graphiti_core-0.21.0rc11}/tests/utils/maintenance/test_node_operations.py
RENAMED
|
@@ -27,6 +27,8 @@ from graphiti_core.utils.maintenance.dedup_helpers import (
|
|
|
27
27
|
from graphiti_core.utils.maintenance.node_operations import (
|
|
28
28
|
_collect_candidate_nodes,
|
|
29
29
|
_resolve_with_llm,
|
|
30
|
+
extract_attributes_from_node,
|
|
31
|
+
extract_attributes_from_nodes,
|
|
30
32
|
resolve_extracted_nodes,
|
|
31
33
|
)
|
|
32
34
|
|
|
@@ -477,3 +479,183 @@ async def test_resolve_with_llm_invalid_duplicate_idx_defaults_to_extracted(monk
|
|
|
477
479
|
assert state.resolved_nodes[0] == extracted
|
|
478
480
|
assert state.uuid_map[extracted.uuid] == extracted.uuid
|
|
479
481
|
assert state.duplicate_pairs == []
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
@pytest.mark.asyncio
|
|
485
|
+
async def test_extract_attributes_without_callback_generates_summary():
|
|
486
|
+
"""Test that summary is generated when no callback is provided (default behavior)."""
|
|
487
|
+
llm_client = MagicMock()
|
|
488
|
+
llm_client.generate_response = AsyncMock(
|
|
489
|
+
return_value={'summary': 'Generated summary', 'attributes': {}}
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
node = EntityNode(name='Test Node', group_id='group', labels=['Entity'], summary='Old summary')
|
|
493
|
+
episode = _make_episode()
|
|
494
|
+
|
|
495
|
+
result = await extract_attributes_from_node(
|
|
496
|
+
llm_client,
|
|
497
|
+
node,
|
|
498
|
+
episode=episode,
|
|
499
|
+
previous_episodes=[],
|
|
500
|
+
entity_type=None,
|
|
501
|
+
ensure_ascii=False,
|
|
502
|
+
should_summarize_node=None, # No callback provided
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# Summary should be generated
|
|
506
|
+
assert result.summary == 'Generated summary'
|
|
507
|
+
# LLM should have been called for summary
|
|
508
|
+
assert llm_client.generate_response.call_count == 1
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
@pytest.mark.asyncio
|
|
512
|
+
async def test_extract_attributes_with_callback_skip_summary():
|
|
513
|
+
"""Test that summary is NOT regenerated when callback returns False."""
|
|
514
|
+
llm_client = MagicMock()
|
|
515
|
+
llm_client.generate_response = AsyncMock(
|
|
516
|
+
return_value={'summary': 'This should not be used', 'attributes': {}}
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
node = EntityNode(name='Test Node', group_id='group', labels=['Entity'], summary='Old summary')
|
|
520
|
+
episode = _make_episode()
|
|
521
|
+
|
|
522
|
+
# Callback that always returns False (skip summary generation)
|
|
523
|
+
async def skip_summary_filter(node: EntityNode) -> bool:
|
|
524
|
+
return False
|
|
525
|
+
|
|
526
|
+
result = await extract_attributes_from_node(
|
|
527
|
+
llm_client,
|
|
528
|
+
node,
|
|
529
|
+
episode=episode,
|
|
530
|
+
previous_episodes=[],
|
|
531
|
+
entity_type=None,
|
|
532
|
+
ensure_ascii=False,
|
|
533
|
+
should_summarize_node=skip_summary_filter,
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Summary should remain unchanged
|
|
537
|
+
assert result.summary == 'Old summary'
|
|
538
|
+
# LLM should NOT have been called for summary
|
|
539
|
+
assert llm_client.generate_response.call_count == 0
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
@pytest.mark.asyncio
|
|
543
|
+
async def test_extract_attributes_with_callback_generate_summary():
|
|
544
|
+
"""Test that summary is regenerated when callback returns True."""
|
|
545
|
+
llm_client = MagicMock()
|
|
546
|
+
llm_client.generate_response = AsyncMock(
|
|
547
|
+
return_value={'summary': 'New generated summary', 'attributes': {}}
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
node = EntityNode(name='Test Node', group_id='group', labels=['Entity'], summary='Old summary')
|
|
551
|
+
episode = _make_episode()
|
|
552
|
+
|
|
553
|
+
# Callback that always returns True (generate summary)
|
|
554
|
+
async def generate_summary_filter(node: EntityNode) -> bool:
|
|
555
|
+
return True
|
|
556
|
+
|
|
557
|
+
result = await extract_attributes_from_node(
|
|
558
|
+
llm_client,
|
|
559
|
+
node,
|
|
560
|
+
episode=episode,
|
|
561
|
+
previous_episodes=[],
|
|
562
|
+
entity_type=None,
|
|
563
|
+
ensure_ascii=False,
|
|
564
|
+
should_summarize_node=generate_summary_filter,
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
# Summary should be updated
|
|
568
|
+
assert result.summary == 'New generated summary'
|
|
569
|
+
# LLM should have been called for summary
|
|
570
|
+
assert llm_client.generate_response.call_count == 1
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
@pytest.mark.asyncio
|
|
574
|
+
async def test_extract_attributes_with_selective_callback():
|
|
575
|
+
"""Test callback that selectively skips summaries based on node properties."""
|
|
576
|
+
llm_client = MagicMock()
|
|
577
|
+
llm_client.generate_response = AsyncMock(
|
|
578
|
+
return_value={'summary': 'Generated summary', 'attributes': {}}
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
user_node = EntityNode(name='User', group_id='group', labels=['Entity', 'User'], summary='Old')
|
|
582
|
+
topic_node = EntityNode(
|
|
583
|
+
name='Topic', group_id='group', labels=['Entity', 'Topic'], summary='Old'
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
episode = _make_episode()
|
|
587
|
+
|
|
588
|
+
# Callback that skips User nodes but generates for others
|
|
589
|
+
async def selective_filter(node: EntityNode) -> bool:
|
|
590
|
+
return 'User' not in node.labels
|
|
591
|
+
|
|
592
|
+
result_user = await extract_attributes_from_node(
|
|
593
|
+
llm_client,
|
|
594
|
+
user_node,
|
|
595
|
+
episode=episode,
|
|
596
|
+
previous_episodes=[],
|
|
597
|
+
entity_type=None,
|
|
598
|
+
ensure_ascii=False,
|
|
599
|
+
should_summarize_node=selective_filter,
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
result_topic = await extract_attributes_from_node(
|
|
603
|
+
llm_client,
|
|
604
|
+
topic_node,
|
|
605
|
+
episode=episode,
|
|
606
|
+
previous_episodes=[],
|
|
607
|
+
entity_type=None,
|
|
608
|
+
ensure_ascii=False,
|
|
609
|
+
should_summarize_node=selective_filter,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
# User summary should remain unchanged
|
|
613
|
+
assert result_user.summary == 'Old'
|
|
614
|
+
# Topic summary should be generated
|
|
615
|
+
assert result_topic.summary == 'Generated summary'
|
|
616
|
+
# LLM should have been called only once (for topic)
|
|
617
|
+
assert llm_client.generate_response.call_count == 1
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
@pytest.mark.asyncio
|
|
621
|
+
async def test_extract_attributes_from_nodes_with_callback():
|
|
622
|
+
"""Test that callback is properly passed through extract_attributes_from_nodes."""
|
|
623
|
+
clients, _ = _make_clients()
|
|
624
|
+
clients.llm_client.generate_response = AsyncMock(
|
|
625
|
+
return_value={'summary': 'New summary', 'attributes': {}}
|
|
626
|
+
)
|
|
627
|
+
clients.embedder.create = AsyncMock(return_value=[0.1, 0.2, 0.3])
|
|
628
|
+
clients.embedder.create_batch = AsyncMock(return_value=[[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]])
|
|
629
|
+
|
|
630
|
+
node1 = EntityNode(name='Node1', group_id='group', labels=['Entity', 'User'], summary='Old1')
|
|
631
|
+
node2 = EntityNode(name='Node2', group_id='group', labels=['Entity', 'Topic'], summary='Old2')
|
|
632
|
+
|
|
633
|
+
episode = _make_episode()
|
|
634
|
+
|
|
635
|
+
call_tracker = []
|
|
636
|
+
|
|
637
|
+
# Callback that tracks which nodes it's called with
|
|
638
|
+
async def tracking_filter(node: EntityNode) -> bool:
|
|
639
|
+
call_tracker.append(node.name)
|
|
640
|
+
return 'User' not in node.labels
|
|
641
|
+
|
|
642
|
+
results = await extract_attributes_from_nodes(
|
|
643
|
+
clients,
|
|
644
|
+
[node1, node2],
|
|
645
|
+
episode=episode,
|
|
646
|
+
previous_episodes=[],
|
|
647
|
+
entity_types=None,
|
|
648
|
+
should_summarize_node=tracking_filter,
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
# Callback should have been called for both nodes
|
|
652
|
+
assert len(call_tracker) == 2
|
|
653
|
+
assert 'Node1' in call_tracker
|
|
654
|
+
assert 'Node2' in call_tracker
|
|
655
|
+
|
|
656
|
+
# Node1 (User) should keep old summary, Node2 (Topic) should get new summary
|
|
657
|
+
node1_result = next(n for n in results if n.name == 'Node1')
|
|
658
|
+
node2_result = next(n for n in results if n.name == 'Node2')
|
|
659
|
+
|
|
660
|
+
assert node1_result.summary == 'Old1'
|
|
661
|
+
assert node2_result.summary == 'New summary'
|