graphiti-core 0.21.0rc5__tar.gz → 0.30.0rc0__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.0rc0/AGENTS.md +21 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/Makefile +2 -2
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/PKG-INFO +1 -1
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/docker-compose.test.yml +1 -1
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/graphiti.py +2 -5
- graphiti_core-0.30.0rc0/graphiti_core/utils/maintenance/dedup_helpers.py +257 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/maintenance/edge_operations.py +14 -26
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/maintenance/node_operations.py +137 -66
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/pyproject.toml +1 -1
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/test_edge_int.py +1 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/test_node_int.py +2 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/utils/maintenance/test_edge_operations.py +50 -0
- graphiti_core-0.30.0rc0/tests/utils/maintenance/test_node_operations.py +341 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/uv.lock +2 -2
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.env.example +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/dependabot.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/pull_request_template.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/secret_scanning.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/ai-moderator.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/cla.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/claude-code-review.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/claude.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/codeql.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/lint.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/mcp-server-docker.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/release-graphiti-core.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/typecheck.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.github/workflows/unit_tests.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/.gitignore +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/CLAUDE.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/CODE_OF_CONDUCT.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/CONTRIBUTING.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/Dockerfile +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/LICENSE +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/README.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/SECURITY.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/Zep-CLA.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/conftest.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/depot.json +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/docker-compose.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/ellipsis.yaml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/data/manybirds_products.json +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/ecommerce/runner.ipynb +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/ecommerce/runner.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/langgraph-agent/agent.ipynb +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/langgraph-agent/tinybirds-jess.png +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/podcast/podcast_runner.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/podcast/podcast_transcript.txt +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/podcast/transcript_parser.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/quickstart/README.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/quickstart/quickstart_falkordb.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/quickstart/quickstart_neo4j.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/quickstart/quickstart_neptune.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/quickstart/requirements.txt +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/wizard_of_oz/parser.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/wizard_of_oz/runner.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/examples/wizard_of_oz/woo.txt +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/cross_encoder/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/cross_encoder/bge_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/cross_encoder/client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/cross_encoder/gemini_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/cross_encoder/openai_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/driver/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/driver/driver.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/driver/falkordb_driver.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/driver/kuzu_driver.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/driver/neo4j_driver.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/driver/neptune_driver.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/edges.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/embedder/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/embedder/azure_openai.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/embedder/client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/embedder/gemini.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/embedder/openai.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/embedder/voyage.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/errors.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/graph_queries.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/graphiti_types.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/helpers.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/anthropic_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/azure_openai_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/config.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/errors.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/gemini_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/groq_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/openai_base_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/openai_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/openai_generic_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/llm_client/utils.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/migrations/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/models/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/models/edges/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/models/edges/edge_db_queries.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/models/nodes/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/models/nodes/node_db_queries.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/nodes.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/dedupe_edges.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/dedupe_nodes.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/eval.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/extract_edge_dates.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/extract_edges.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/extract_nodes.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/invalidate_edges.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/lib.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/models.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/prompt_helpers.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/prompts/summarize_nodes.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/py.typed +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/search/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/search/search.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/search/search_config.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/search/search_config_recipes.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/search/search_filters.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/search/search_helpers.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/search/search_utils.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/telemetry/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/telemetry/telemetry.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/bulk_utils.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/datetime_utils.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/maintenance/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/maintenance/community_operations.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/maintenance/graph_data_operations.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/maintenance/temporal_operations.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/maintenance/utils.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/graphiti_core/utils/ontology_utils/entity_types_utils.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/images/arxiv-screenshot.png +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/images/graphiti-graph-intro.gif +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/images/graphiti-intro-slides-stock-2.gif +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/images/simple_graph.svg +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/.env.example +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/.python-version +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/Dockerfile +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/README.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/cursor_rules.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/docker-compose.yml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/graphiti_mcp_server.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/mcp_config_sse_example.json +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/mcp_config_stdio_example.json +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/pyproject.toml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/mcp_server/uv.lock +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/poetry.lock +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/py.typed +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/pytest.ini +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/.env.example +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/Makefile +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/README.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/config.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/dto/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/dto/common.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/dto/ingest.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/dto/retrieve.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/main.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/routers/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/routers/ingest.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/routers/retrieve.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/graph_service/zep_graphiti.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/pyproject.toml +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/server/uv.lock +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/signatures/version1/cla.json +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/cross_encoder/test_bge_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/cross_encoder/test_gemini_reranker_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/driver/__init__.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/driver/test_falkordb_driver.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/embedder/embedder_fixtures.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/embedder/test_gemini.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/embedder/test_openai.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/embedder/test_voyage.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/evals/data/longmemeval_data/README.md +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/evals/data/longmemeval_data/longmemeval_oracle.json +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/evals/eval_cli.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/evals/eval_e2e_graph_building.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/evals/pytest.ini +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/evals/utils.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/helpers_test.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/llm_client/test_anthropic_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/llm_client/test_anthropic_client_int.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/llm_client/test_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/llm_client/test_errors.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/llm_client/test_gemini_client.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/test_entity_exclusion_int.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/test_graphiti_int.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/test_graphiti_mock.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/utils/maintenance/test_temporal_operations_int.py +0 -0
- {graphiti_core-0.21.0rc5 → graphiti_core-0.30.0rc0}/tests/utils/search/search_utils_test.py +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Repository Guidelines
|
|
2
|
+
|
|
3
|
+
## Project Structure & Module Organization
|
|
4
|
+
Graphiti's core library lives under `graphiti_core/`, split into domain modules such as `nodes.py`, `edges.py`, `models/`, and `search/` for retrieval pipelines. Service adapters and API glue reside in `server/graph_service/`, while the MCP integration lives in `mcp_server/`. Shared assets and collateral sit in `images/` and `examples/`. Tests cover the package via `tests/`, with configuration in `conftest.py`, `pytest.ini`, and Docker compose files for optional services. Tooling manifests live at the repo root, including `pyproject.toml`, `Makefile`, and deployment compose files.
|
|
5
|
+
|
|
6
|
+
## Build, Test, and Development Commands
|
|
7
|
+
- `uv sync --extra dev`: install the dev environment declared in `pyproject.toml`.
|
|
8
|
+
- `make format`: run `ruff` to sort imports and apply the canonical formatter.
|
|
9
|
+
- `make lint`: execute `ruff` plus `pyright` type checks against `graphiti_core`.
|
|
10
|
+
- `make test`: run the full `pytest` suite (`uv run pytest`).
|
|
11
|
+
- `uv run pytest tests/path/test_file.py`: target a specific module or test selection.
|
|
12
|
+
- `docker-compose -f docker-compose.test.yml up`: provision local graph/search dependencies for integration flows.
|
|
13
|
+
|
|
14
|
+
## Coding Style & Naming Conventions
|
|
15
|
+
Python code uses 4-space indentation, 100-character lines, and prefers single quotes as configured in `pyproject.toml`. Modules, files, and functions stay snake_case; Pydantic models in `graphiti_core/models` use PascalCase with explicit type hints. Keep side-effectful code inside drivers or adapters (`graphiti_core/driver`, `graphiti_core/utils`) and rely on pure helpers elsewhere. Run `make format` before committing to normalize imports and docstring formatting.
|
|
16
|
+
|
|
17
|
+
## Testing Guidelines
|
|
18
|
+
Author tests alongside features under `tests/`, naming files `test_<feature>.py` and functions `test_<behavior>`. Use `@pytest.mark.integration` for database-reliant scenarios so CI can gate them. Reproduce regressions with a failing test first and validate fixes via `uv run pytest -k "pattern"`. Start required backing services through `docker-compose.test.yml` when running integration suites locally.
|
|
19
|
+
|
|
20
|
+
## Commit & Pull Request Guidelines
|
|
21
|
+
Commits use an imperative, present-tense summary (for example, `add async cache invalidation`) optionally suffixed with the PR number as seen in history (`(#927)`). Squash fixups and keep unrelated changes isolated. Pull requests should include: a concise description, linked tracking issue, notes about schema or API impacts, and screenshots or logs when behavior changes. Confirm `make lint` and `make test` pass locally, and update docs or examples when public interfaces shift.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphiti-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.30.0rc0
|
|
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
|
|
@@ -79,7 +79,6 @@ from graphiti_core.utils.maintenance.community_operations import (
|
|
|
79
79
|
update_community,
|
|
80
80
|
)
|
|
81
81
|
from graphiti_core.utils.maintenance.edge_operations import (
|
|
82
|
-
build_duplicate_of_edges,
|
|
83
82
|
build_episodic_edges,
|
|
84
83
|
extract_edges,
|
|
85
84
|
resolve_extracted_edge,
|
|
@@ -503,7 +502,7 @@ class Graphiti:
|
|
|
503
502
|
)
|
|
504
503
|
|
|
505
504
|
# Extract edges and resolve nodes
|
|
506
|
-
(nodes, uuid_map,
|
|
505
|
+
(nodes, uuid_map, _), extracted_edges = await semaphore_gather(
|
|
507
506
|
resolve_extracted_nodes(
|
|
508
507
|
self.clients,
|
|
509
508
|
extracted_nodes,
|
|
@@ -540,9 +539,7 @@ class Graphiti:
|
|
|
540
539
|
max_coroutines=self.max_coroutines,
|
|
541
540
|
)
|
|
542
541
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
entity_edges = resolved_edges + invalidated_edges + duplicate_of_edges
|
|
542
|
+
entity_edges = resolved_edges + invalidated_edges
|
|
546
543
|
|
|
547
544
|
episodic_edges = build_episodic_edges(nodes, episode.uuid, now)
|
|
548
545
|
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024, Zep Software, Inc.
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import math
|
|
20
|
+
import re
|
|
21
|
+
from collections import defaultdict
|
|
22
|
+
from collections.abc import Iterable
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from functools import lru_cache
|
|
25
|
+
from hashlib import blake2b
|
|
26
|
+
from typing import TYPE_CHECKING
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from graphiti_core.nodes import EntityNode
|
|
30
|
+
|
|
31
|
+
_NAME_ENTROPY_THRESHOLD = 1.5
|
|
32
|
+
_MIN_NAME_LENGTH = 6
|
|
33
|
+
_MIN_TOKEN_COUNT = 2
|
|
34
|
+
_FUZZY_JACCARD_THRESHOLD = 0.9
|
|
35
|
+
_MINHASH_PERMUTATIONS = 32
|
|
36
|
+
_MINHASH_BAND_SIZE = 4
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _normalize_string_exact(name: str) -> str:
|
|
40
|
+
"""Lowercase text and collapse whitespace so equal names map to the same key."""
|
|
41
|
+
normalized = re.sub(r'[\s]+', ' ', name.lower())
|
|
42
|
+
return normalized.strip()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _normalize_name_for_fuzzy(name: str) -> str:
|
|
46
|
+
"""Produce a fuzzier form that keeps alphanumerics and apostrophes for n-gram shingles."""
|
|
47
|
+
normalized = re.sub(r"[^a-z0-9' ]", ' ', _normalize_string_exact(name))
|
|
48
|
+
normalized = normalized.strip()
|
|
49
|
+
return re.sub(r'[\s]+', ' ', normalized)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _name_entropy(normalized_name: str) -> float:
|
|
53
|
+
"""Approximate text specificity using Shannon entropy over characters.
|
|
54
|
+
|
|
55
|
+
We strip spaces, count how often each character appears, and sum
|
|
56
|
+
probability * -log2(probability). Short or repetitive names yield low
|
|
57
|
+
entropy, which signals we should defer resolution to the LLM instead of
|
|
58
|
+
trusting fuzzy similarity.
|
|
59
|
+
"""
|
|
60
|
+
if not normalized_name:
|
|
61
|
+
return 0.0
|
|
62
|
+
|
|
63
|
+
counts: dict[str, int] = {}
|
|
64
|
+
for char in normalized_name.replace(' ', ''):
|
|
65
|
+
counts[char] = counts.get(char, 0) + 1
|
|
66
|
+
|
|
67
|
+
total = sum(counts.values())
|
|
68
|
+
if total == 0:
|
|
69
|
+
return 0.0
|
|
70
|
+
|
|
71
|
+
entropy = 0.0
|
|
72
|
+
for count in counts.values():
|
|
73
|
+
probability = count / total
|
|
74
|
+
entropy -= probability * math.log2(probability)
|
|
75
|
+
|
|
76
|
+
return entropy
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _has_high_entropy(normalized_name: str) -> bool:
|
|
80
|
+
"""Filter out very short or low-entropy names that are unreliable for fuzzy matching."""
|
|
81
|
+
token_count = len(normalized_name.split())
|
|
82
|
+
if len(normalized_name) < _MIN_NAME_LENGTH and token_count < _MIN_TOKEN_COUNT:
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
return _name_entropy(normalized_name) >= _NAME_ENTROPY_THRESHOLD
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _shingles(normalized_name: str) -> set[str]:
|
|
89
|
+
"""Create 3-gram shingles from the normalized name for MinHash calculations."""
|
|
90
|
+
cleaned = normalized_name.replace(' ', '')
|
|
91
|
+
if len(cleaned) < 2:
|
|
92
|
+
return {cleaned} if cleaned else set()
|
|
93
|
+
|
|
94
|
+
return {cleaned[i : i + 3] for i in range(len(cleaned) - 2)}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _hash_shingle(shingle: str, seed: int) -> int:
|
|
98
|
+
"""Generate a deterministic 64-bit hash for a shingle given the permutation seed."""
|
|
99
|
+
digest = blake2b(f'{seed}:{shingle}'.encode(), digest_size=8)
|
|
100
|
+
return int.from_bytes(digest.digest(), 'big')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _minhash_signature(shingles: Iterable[str]) -> tuple[int, ...]:
|
|
104
|
+
"""Compute the MinHash signature for the shingle set across predefined permutations."""
|
|
105
|
+
if not shingles:
|
|
106
|
+
return tuple()
|
|
107
|
+
|
|
108
|
+
seeds = range(_MINHASH_PERMUTATIONS)
|
|
109
|
+
signature: list[int] = []
|
|
110
|
+
for seed in seeds:
|
|
111
|
+
min_hash = min(_hash_shingle(shingle, seed) for shingle in shingles)
|
|
112
|
+
signature.append(min_hash)
|
|
113
|
+
|
|
114
|
+
return tuple(signature)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _lsh_bands(signature: Iterable[int]) -> list[tuple[int, ...]]:
|
|
118
|
+
"""Split the MinHash signature into fixed-size bands for locality-sensitive hashing."""
|
|
119
|
+
signature_list = list(signature)
|
|
120
|
+
if not signature_list:
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
bands: list[tuple[int, ...]] = []
|
|
124
|
+
for start in range(0, len(signature_list), _MINHASH_BAND_SIZE):
|
|
125
|
+
band = tuple(signature_list[start : start + _MINHASH_BAND_SIZE])
|
|
126
|
+
if len(band) == _MINHASH_BAND_SIZE:
|
|
127
|
+
bands.append(band)
|
|
128
|
+
return bands
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _jaccard_similarity(a: set[str], b: set[str]) -> float:
|
|
132
|
+
"""Return the Jaccard similarity between two shingle sets, handling empty edge cases."""
|
|
133
|
+
if not a and not b:
|
|
134
|
+
return 1.0
|
|
135
|
+
if not a or not b:
|
|
136
|
+
return 0.0
|
|
137
|
+
|
|
138
|
+
intersection = len(a.intersection(b))
|
|
139
|
+
union = len(a.union(b))
|
|
140
|
+
return intersection / union if union else 0.0
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@lru_cache(maxsize=512)
|
|
144
|
+
def _cached_shingles(name: str) -> set[str]:
|
|
145
|
+
"""Cache shingle sets per normalized name to avoid recomputation within a worker."""
|
|
146
|
+
return _shingles(name)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@dataclass
|
|
150
|
+
class DedupCandidateIndexes:
|
|
151
|
+
"""Precomputed lookup structures that drive entity deduplication heuristics."""
|
|
152
|
+
|
|
153
|
+
existing_nodes: list[EntityNode]
|
|
154
|
+
nodes_by_uuid: dict[str, EntityNode]
|
|
155
|
+
normalized_existing: defaultdict[str, list[EntityNode]]
|
|
156
|
+
shingles_by_candidate: dict[str, set[str]]
|
|
157
|
+
lsh_buckets: defaultdict[tuple[int, tuple[int, ...]], list[str]]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass
|
|
161
|
+
class DedupResolutionState:
|
|
162
|
+
"""Mutable resolution bookkeeping shared across deterministic and LLM passes."""
|
|
163
|
+
|
|
164
|
+
resolved_nodes: list[EntityNode | None]
|
|
165
|
+
uuid_map: dict[str, str]
|
|
166
|
+
unresolved_indices: list[int]
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _build_candidate_indexes(existing_nodes: list[EntityNode]) -> DedupCandidateIndexes:
|
|
170
|
+
"""Precompute exact and fuzzy lookup structures once per dedupe run."""
|
|
171
|
+
normalized_existing: defaultdict[str, list[EntityNode]] = defaultdict(list)
|
|
172
|
+
nodes_by_uuid: dict[str, EntityNode] = {}
|
|
173
|
+
shingles_by_candidate: dict[str, set[str]] = {}
|
|
174
|
+
lsh_buckets: defaultdict[tuple[int, tuple[int, ...]], list[str]] = defaultdict(list)
|
|
175
|
+
|
|
176
|
+
for candidate in existing_nodes:
|
|
177
|
+
normalized = _normalize_string_exact(candidate.name)
|
|
178
|
+
normalized_existing[normalized].append(candidate)
|
|
179
|
+
nodes_by_uuid[candidate.uuid] = candidate
|
|
180
|
+
|
|
181
|
+
shingles = _cached_shingles(_normalize_name_for_fuzzy(candidate.name))
|
|
182
|
+
shingles_by_candidate[candidate.uuid] = shingles
|
|
183
|
+
|
|
184
|
+
signature = _minhash_signature(shingles)
|
|
185
|
+
for band_index, band in enumerate(_lsh_bands(signature)):
|
|
186
|
+
lsh_buckets[(band_index, band)].append(candidate.uuid)
|
|
187
|
+
|
|
188
|
+
return DedupCandidateIndexes(
|
|
189
|
+
existing_nodes=existing_nodes,
|
|
190
|
+
nodes_by_uuid=nodes_by_uuid,
|
|
191
|
+
normalized_existing=normalized_existing,
|
|
192
|
+
shingles_by_candidate=shingles_by_candidate,
|
|
193
|
+
lsh_buckets=lsh_buckets,
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _resolve_with_similarity(
|
|
198
|
+
extracted_nodes: list[EntityNode],
|
|
199
|
+
indexes: DedupCandidateIndexes,
|
|
200
|
+
state: DedupResolutionState,
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Attempt deterministic resolution using exact name hits and fuzzy MinHash comparisons."""
|
|
203
|
+
for idx, node in enumerate(extracted_nodes):
|
|
204
|
+
normalized_exact = _normalize_string_exact(node.name)
|
|
205
|
+
normalized_fuzzy = _normalize_name_for_fuzzy(node.name)
|
|
206
|
+
|
|
207
|
+
if not _has_high_entropy(normalized_fuzzy):
|
|
208
|
+
state.unresolved_indices.append(idx)
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
existing_matches = indexes.normalized_existing.get(normalized_exact, [])
|
|
212
|
+
if len(existing_matches) == 1:
|
|
213
|
+
match = existing_matches[0]
|
|
214
|
+
state.resolved_nodes[idx] = match
|
|
215
|
+
state.uuid_map[node.uuid] = match.uuid
|
|
216
|
+
continue
|
|
217
|
+
if len(existing_matches) > 1:
|
|
218
|
+
state.unresolved_indices.append(idx)
|
|
219
|
+
continue
|
|
220
|
+
|
|
221
|
+
shingles = _cached_shingles(normalized_fuzzy)
|
|
222
|
+
signature = _minhash_signature(shingles)
|
|
223
|
+
candidate_ids: set[str] = set()
|
|
224
|
+
for band_index, band in enumerate(_lsh_bands(signature)):
|
|
225
|
+
candidate_ids.update(indexes.lsh_buckets.get((band_index, band), []))
|
|
226
|
+
|
|
227
|
+
best_candidate: EntityNode | None = None
|
|
228
|
+
best_score = 0.0
|
|
229
|
+
for candidate_id in candidate_ids:
|
|
230
|
+
candidate_shingles = indexes.shingles_by_candidate.get(candidate_id, set())
|
|
231
|
+
score = _jaccard_similarity(shingles, candidate_shingles)
|
|
232
|
+
if score > best_score:
|
|
233
|
+
best_score = score
|
|
234
|
+
best_candidate = indexes.nodes_by_uuid.get(candidate_id)
|
|
235
|
+
|
|
236
|
+
if best_candidate is not None and best_score >= _FUZZY_JACCARD_THRESHOLD:
|
|
237
|
+
state.resolved_nodes[idx] = best_candidate
|
|
238
|
+
state.uuid_map[node.uuid] = best_candidate.uuid
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
state.unresolved_indices.append(idx)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
__all__ = [
|
|
245
|
+
'DedupCandidateIndexes',
|
|
246
|
+
'DedupResolutionState',
|
|
247
|
+
'_normalize_string_exact',
|
|
248
|
+
'_normalize_name_for_fuzzy',
|
|
249
|
+
'_has_high_entropy',
|
|
250
|
+
'_minhash_signature',
|
|
251
|
+
'_lsh_bands',
|
|
252
|
+
'_jaccard_similarity',
|
|
253
|
+
'_cached_shingles',
|
|
254
|
+
'_FUZZY_JACCARD_THRESHOLD',
|
|
255
|
+
'_build_candidate_indexes',
|
|
256
|
+
'_resolve_with_similarity',
|
|
257
|
+
]
|
|
@@ -41,6 +41,7 @@ from graphiti_core.search.search_config import SearchResults
|
|
|
41
41
|
from graphiti_core.search.search_config_recipes import EDGE_HYBRID_SEARCH_RRF
|
|
42
42
|
from graphiti_core.search.search_filters import SearchFilters
|
|
43
43
|
from graphiti_core.utils.datetime_utils import ensure_utc, utc_now
|
|
44
|
+
from graphiti_core.utils.maintenance.dedup_helpers import _normalize_string_exact
|
|
44
45
|
|
|
45
46
|
logger = logging.getLogger(__name__)
|
|
46
47
|
|
|
@@ -65,32 +66,6 @@ def build_episodic_edges(
|
|
|
65
66
|
return episodic_edges
|
|
66
67
|
|
|
67
68
|
|
|
68
|
-
def build_duplicate_of_edges(
|
|
69
|
-
episode: EpisodicNode,
|
|
70
|
-
created_at: datetime,
|
|
71
|
-
duplicate_nodes: list[tuple[EntityNode, EntityNode]],
|
|
72
|
-
) -> list[EntityEdge]:
|
|
73
|
-
is_duplicate_of_edges: list[EntityEdge] = []
|
|
74
|
-
for source_node, target_node in duplicate_nodes:
|
|
75
|
-
if source_node.uuid == target_node.uuid:
|
|
76
|
-
continue
|
|
77
|
-
|
|
78
|
-
is_duplicate_of_edges.append(
|
|
79
|
-
EntityEdge(
|
|
80
|
-
source_node_uuid=source_node.uuid,
|
|
81
|
-
target_node_uuid=target_node.uuid,
|
|
82
|
-
name='IS_DUPLICATE_OF',
|
|
83
|
-
group_id=episode.group_id,
|
|
84
|
-
fact=f'{source_node.name} is a duplicate of {target_node.name}',
|
|
85
|
-
episodes=[episode.uuid],
|
|
86
|
-
created_at=created_at,
|
|
87
|
-
valid_at=created_at,
|
|
88
|
-
)
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
return is_duplicate_of_edges
|
|
92
|
-
|
|
93
|
-
|
|
94
69
|
def build_community_edges(
|
|
95
70
|
entity_nodes: list[EntityNode],
|
|
96
71
|
community_node: CommunityNode,
|
|
@@ -423,6 +398,19 @@ async def resolve_extracted_edge(
|
|
|
423
398
|
if len(related_edges) == 0 and len(existing_edges) == 0:
|
|
424
399
|
return extracted_edge, [], []
|
|
425
400
|
|
|
401
|
+
# Fast path: if the fact text and endpoints already exist verbatim, reuse the matching edge.
|
|
402
|
+
normalized_fact = _normalize_string_exact(extracted_edge.fact)
|
|
403
|
+
for edge in related_edges:
|
|
404
|
+
if (
|
|
405
|
+
edge.source_node_uuid == extracted_edge.source_node_uuid
|
|
406
|
+
and edge.target_node_uuid == extracted_edge.target_node_uuid
|
|
407
|
+
and _normalize_string_exact(edge.fact) == normalized_fact
|
|
408
|
+
):
|
|
409
|
+
resolved = edge
|
|
410
|
+
if episode is not None and episode.uuid not in resolved.episodes:
|
|
411
|
+
resolved.episodes.append(episode.uuid)
|
|
412
|
+
return resolved, [], []
|
|
413
|
+
|
|
426
414
|
start = time()
|
|
427
415
|
|
|
428
416
|
# Prepare context for LLM
|