cognee 0.4.0__py3-none-any.whl → 0.5.0__py3-none-any.whl
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.
- cognee/__init__.py +1 -0
- cognee/api/client.py +9 -5
- cognee/api/v1/add/add.py +2 -1
- cognee/api/v1/add/routers/get_add_router.py +3 -1
- cognee/api/v1/cognify/cognify.py +24 -16
- cognee/api/v1/cognify/routers/__init__.py +0 -1
- cognee/api/v1/cognify/routers/get_cognify_router.py +30 -1
- cognee/api/v1/datasets/routers/get_datasets_router.py +3 -3
- cognee/api/v1/ontologies/__init__.py +4 -0
- cognee/api/v1/ontologies/ontologies.py +158 -0
- cognee/api/v1/ontologies/routers/__init__.py +0 -0
- cognee/api/v1/ontologies/routers/get_ontology_router.py +109 -0
- cognee/api/v1/permissions/routers/get_permissions_router.py +41 -1
- cognee/api/v1/search/search.py +4 -0
- cognee/api/v1/ui/node_setup.py +360 -0
- cognee/api/v1/ui/npm_utils.py +50 -0
- cognee/api/v1/ui/ui.py +38 -68
- cognee/cli/commands/cognify_command.py +8 -1
- cognee/cli/config.py +1 -1
- cognee/context_global_variables.py +86 -9
- cognee/eval_framework/Dockerfile +29 -0
- cognee/eval_framework/answer_generation/answer_generation_executor.py +10 -0
- cognee/eval_framework/answer_generation/run_question_answering_module.py +1 -1
- cognee/eval_framework/corpus_builder/task_getters/get_cascade_graph_tasks.py +0 -2
- cognee/eval_framework/corpus_builder/task_getters/get_default_tasks_by_indices.py +4 -4
- cognee/eval_framework/eval_config.py +2 -2
- cognee/eval_framework/modal_run_eval.py +16 -28
- cognee/infrastructure/databases/cache/config.py +3 -1
- cognee/infrastructure/databases/cache/fscache/FsCacheAdapter.py +151 -0
- cognee/infrastructure/databases/cache/get_cache_engine.py +20 -10
- cognee/infrastructure/databases/dataset_database_handler/__init__.py +3 -0
- cognee/infrastructure/databases/dataset_database_handler/dataset_database_handler_interface.py +80 -0
- cognee/infrastructure/databases/dataset_database_handler/supported_dataset_database_handlers.py +18 -0
- cognee/infrastructure/databases/dataset_database_handler/use_dataset_database_handler.py +10 -0
- cognee/infrastructure/databases/exceptions/exceptions.py +16 -0
- cognee/infrastructure/databases/graph/config.py +7 -0
- cognee/infrastructure/databases/graph/get_graph_engine.py +3 -0
- cognee/infrastructure/databases/graph/graph_db_interface.py +15 -0
- cognee/infrastructure/databases/graph/kuzu/KuzuDatasetDatabaseHandler.py +81 -0
- cognee/infrastructure/databases/graph/kuzu/adapter.py +228 -0
- cognee/infrastructure/databases/graph/neo4j_driver/Neo4jAuraDevDatasetDatabaseHandler.py +168 -0
- cognee/infrastructure/databases/graph/neo4j_driver/adapter.py +80 -1
- cognee/infrastructure/databases/hybrid/neptune_analytics/NeptuneAnalyticsAdapter.py +9 -0
- cognee/infrastructure/databases/utils/__init__.py +3 -0
- cognee/infrastructure/databases/utils/get_graph_dataset_database_handler.py +10 -0
- cognee/infrastructure/databases/utils/get_or_create_dataset_database.py +66 -18
- cognee/infrastructure/databases/utils/get_vector_dataset_database_handler.py +10 -0
- cognee/infrastructure/databases/utils/resolve_dataset_database_connection_info.py +30 -0
- cognee/infrastructure/databases/vector/config.py +5 -0
- cognee/infrastructure/databases/vector/create_vector_engine.py +6 -1
- cognee/infrastructure/databases/vector/embeddings/FastembedEmbeddingEngine.py +8 -6
- cognee/infrastructure/databases/vector/embeddings/LiteLLMEmbeddingEngine.py +9 -7
- cognee/infrastructure/databases/vector/embeddings/OllamaEmbeddingEngine.py +11 -10
- cognee/infrastructure/databases/vector/lancedb/LanceDBAdapter.py +2 -0
- cognee/infrastructure/databases/vector/lancedb/LanceDBDatasetDatabaseHandler.py +50 -0
- cognee/infrastructure/databases/vector/vector_db_interface.py +35 -0
- cognee/infrastructure/engine/models/Edge.py +13 -1
- cognee/infrastructure/files/storage/s3_config.py +2 -0
- cognee/infrastructure/files/utils/guess_file_type.py +4 -0
- cognee/infrastructure/llm/LLMGateway.py +5 -2
- cognee/infrastructure/llm/config.py +37 -0
- cognee/infrastructure/llm/extraction/knowledge_graph/extract_content_graph.py +2 -2
- cognee/infrastructure/llm/structured_output_framework/baml/baml_src/extraction/acreate_structured_output.py +23 -8
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/anthropic/adapter.py +22 -18
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/bedrock/__init__.py +5 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/bedrock/adapter.py +153 -0
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/gemini/adapter.py +47 -38
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/generic_llm_api/adapter.py +46 -37
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/get_llm_client.py +20 -10
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/mistral/adapter.py +23 -11
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/ollama/adapter.py +36 -23
- cognee/infrastructure/llm/structured_output_framework/litellm_instructor/llm/openai/adapter.py +47 -36
- cognee/infrastructure/loaders/LoaderEngine.py +1 -0
- cognee/infrastructure/loaders/core/__init__.py +2 -1
- cognee/infrastructure/loaders/core/csv_loader.py +93 -0
- cognee/infrastructure/loaders/core/text_loader.py +1 -2
- cognee/infrastructure/loaders/external/advanced_pdf_loader.py +0 -9
- cognee/infrastructure/loaders/supported_loaders.py +2 -1
- cognee/memify_pipelines/create_triplet_embeddings.py +53 -0
- cognee/memify_pipelines/persist_sessions_in_knowledge_graph.py +55 -0
- cognee/modules/chunking/CsvChunker.py +35 -0
- cognee/modules/chunking/models/DocumentChunk.py +2 -1
- cognee/modules/chunking/text_chunker_with_overlap.py +124 -0
- cognee/modules/cognify/config.py +2 -0
- cognee/modules/data/deletion/prune_system.py +52 -2
- cognee/modules/data/methods/__init__.py +1 -0
- cognee/modules/data/methods/create_dataset.py +4 -2
- cognee/modules/data/methods/delete_dataset.py +26 -0
- cognee/modules/data/methods/get_dataset_ids.py +5 -1
- cognee/modules/data/methods/get_unique_data_id.py +68 -0
- cognee/modules/data/methods/get_unique_dataset_id.py +66 -4
- cognee/modules/data/models/Dataset.py +2 -0
- cognee/modules/data/processing/document_types/CsvDocument.py +33 -0
- cognee/modules/data/processing/document_types/__init__.py +1 -0
- cognee/modules/engine/models/Triplet.py +9 -0
- cognee/modules/engine/models/__init__.py +1 -0
- cognee/modules/graph/cognee_graph/CogneeGraph.py +89 -39
- cognee/modules/graph/cognee_graph/CogneeGraphElements.py +8 -3
- cognee/modules/graph/utils/expand_with_nodes_and_edges.py +19 -2
- cognee/modules/graph/utils/resolve_edges_to_text.py +48 -49
- cognee/modules/ingestion/identify.py +4 -4
- cognee/modules/memify/memify.py +1 -7
- cognee/modules/notebooks/operations/run_in_local_sandbox.py +3 -0
- cognee/modules/ontology/rdf_xml/RDFLibOntologyResolver.py +55 -23
- cognee/modules/pipelines/operations/pipeline.py +18 -2
- cognee/modules/pipelines/operations/run_tasks_data_item.py +1 -1
- cognee/modules/retrieval/EntityCompletionRetriever.py +10 -3
- cognee/modules/retrieval/__init__.py +1 -1
- cognee/modules/retrieval/base_graph_retriever.py +7 -3
- cognee/modules/retrieval/base_retriever.py +7 -3
- cognee/modules/retrieval/completion_retriever.py +11 -4
- cognee/modules/retrieval/graph_completion_context_extension_retriever.py +10 -2
- cognee/modules/retrieval/graph_completion_cot_retriever.py +18 -51
- cognee/modules/retrieval/graph_completion_retriever.py +14 -1
- cognee/modules/retrieval/graph_summary_completion_retriever.py +4 -0
- cognee/modules/retrieval/register_retriever.py +10 -0
- cognee/modules/retrieval/registered_community_retrievers.py +1 -0
- cognee/modules/retrieval/temporal_retriever.py +13 -2
- cognee/modules/retrieval/triplet_retriever.py +182 -0
- cognee/modules/retrieval/utils/brute_force_triplet_search.py +43 -11
- cognee/modules/retrieval/utils/completion.py +2 -22
- cognee/modules/run_custom_pipeline/__init__.py +1 -0
- cognee/modules/run_custom_pipeline/run_custom_pipeline.py +76 -0
- cognee/modules/search/methods/get_search_type_tools.py +54 -8
- cognee/modules/search/methods/no_access_control_search.py +4 -0
- cognee/modules/search/methods/search.py +26 -3
- cognee/modules/search/types/SearchType.py +1 -1
- cognee/modules/settings/get_settings.py +19 -0
- cognee/modules/users/methods/create_user.py +12 -27
- cognee/modules/users/methods/get_authenticated_user.py +3 -2
- cognee/modules/users/methods/get_default_user.py +4 -2
- cognee/modules/users/methods/get_user.py +1 -1
- cognee/modules/users/methods/get_user_by_email.py +1 -1
- cognee/modules/users/models/DatasetDatabase.py +24 -3
- cognee/modules/users/models/Tenant.py +6 -7
- cognee/modules/users/models/User.py +6 -5
- cognee/modules/users/models/UserTenant.py +12 -0
- cognee/modules/users/models/__init__.py +1 -0
- cognee/modules/users/permissions/methods/get_all_user_permission_datasets.py +13 -13
- cognee/modules/users/roles/methods/add_user_to_role.py +3 -1
- cognee/modules/users/tenants/methods/__init__.py +1 -0
- cognee/modules/users/tenants/methods/add_user_to_tenant.py +21 -12
- cognee/modules/users/tenants/methods/create_tenant.py +22 -8
- cognee/modules/users/tenants/methods/select_tenant.py +62 -0
- cognee/shared/logging_utils.py +6 -0
- cognee/shared/rate_limiting.py +30 -0
- cognee/tasks/chunks/__init__.py +1 -0
- cognee/tasks/chunks/chunk_by_row.py +94 -0
- cognee/tasks/documents/__init__.py +0 -1
- cognee/tasks/documents/classify_documents.py +2 -0
- cognee/tasks/feedback/generate_improved_answers.py +3 -3
- cognee/tasks/graph/extract_graph_from_data.py +9 -10
- cognee/tasks/ingestion/ingest_data.py +1 -1
- cognee/tasks/memify/__init__.py +2 -0
- cognee/tasks/memify/cognify_session.py +41 -0
- cognee/tasks/memify/extract_user_sessions.py +73 -0
- cognee/tasks/memify/get_triplet_datapoints.py +289 -0
- cognee/tasks/storage/add_data_points.py +142 -2
- cognee/tasks/storage/index_data_points.py +33 -22
- cognee/tasks/storage/index_graph_edges.py +37 -57
- cognee/tests/integration/documents/CsvDocument_test.py +70 -0
- cognee/tests/integration/retrieval/test_triplet_retriever.py +84 -0
- cognee/tests/integration/tasks/test_add_data_points.py +139 -0
- cognee/tests/integration/tasks/test_get_triplet_datapoints.py +69 -0
- cognee/tests/integration/web_url_crawler/test_default_url_crawler.py +1 -1
- cognee/tests/integration/web_url_crawler/test_tavily_crawler.py +1 -1
- cognee/tests/integration/web_url_crawler/test_url_adding_e2e.py +13 -27
- cognee/tests/tasks/entity_extraction/entity_extraction_test.py +1 -1
- cognee/tests/test_add_docling_document.py +2 -2
- cognee/tests/test_cognee_server_start.py +84 -3
- cognee/tests/test_conversation_history.py +68 -5
- cognee/tests/test_data/example_with_header.csv +3 -0
- cognee/tests/test_dataset_database_handler.py +137 -0
- cognee/tests/test_dataset_delete.py +76 -0
- cognee/tests/test_edge_centered_payload.py +170 -0
- cognee/tests/test_edge_ingestion.py +27 -0
- cognee/tests/test_feedback_enrichment.py +1 -1
- cognee/tests/test_library.py +6 -4
- cognee/tests/test_load.py +62 -0
- cognee/tests/test_multi_tenancy.py +165 -0
- cognee/tests/test_parallel_databases.py +2 -0
- cognee/tests/test_pipeline_cache.py +164 -0
- cognee/tests/test_relational_db_migration.py +54 -2
- cognee/tests/test_search_db.py +44 -2
- cognee/tests/unit/api/test_conditional_authentication_endpoints.py +12 -3
- cognee/tests/unit/api/test_ontology_endpoint.py +252 -0
- cognee/tests/unit/infrastructure/databases/cache/test_cache_config.py +5 -0
- cognee/tests/unit/infrastructure/databases/test_index_data_points.py +27 -0
- cognee/tests/unit/infrastructure/databases/test_index_graph_edges.py +14 -16
- cognee/tests/unit/infrastructure/llm/test_llm_config.py +46 -0
- cognee/tests/unit/infrastructure/mock_embedding_engine.py +3 -7
- cognee/tests/unit/infrastructure/test_embedding_rate_limiting_realistic.py +0 -5
- cognee/tests/unit/modules/chunking/test_text_chunker.py +248 -0
- cognee/tests/unit/modules/chunking/test_text_chunker_with_overlap.py +324 -0
- cognee/tests/unit/modules/graph/cognee_graph_elements_test.py +2 -2
- cognee/tests/unit/modules/graph/cognee_graph_test.py +406 -0
- cognee/tests/unit/modules/memify_tasks/test_cognify_session.py +111 -0
- cognee/tests/unit/modules/memify_tasks/test_extract_user_sessions.py +175 -0
- cognee/tests/unit/modules/memify_tasks/test_get_triplet_datapoints.py +214 -0
- cognee/tests/unit/modules/retrieval/graph_completion_retriever_cot_test.py +0 -51
- cognee/tests/unit/modules/retrieval/rag_completion_retriever_test.py +1 -0
- cognee/tests/unit/modules/retrieval/structured_output_test.py +204 -0
- cognee/tests/unit/modules/retrieval/summaries_retriever_test.py +1 -1
- cognee/tests/unit/modules/retrieval/temporal_retriever_test.py +0 -1
- cognee/tests/unit/modules/retrieval/test_brute_force_triplet_search.py +608 -0
- cognee/tests/unit/modules/retrieval/triplet_retriever_test.py +83 -0
- cognee/tests/unit/modules/users/test_conditional_authentication.py +0 -63
- cognee/tests/unit/processing/chunks/chunk_by_row_test.py +52 -0
- cognee/tests/unit/tasks/storage/test_add_data_points.py +288 -0
- {cognee-0.4.0.dist-info → cognee-0.5.0.dist-info}/METADATA +11 -6
- {cognee-0.4.0.dist-info → cognee-0.5.0.dist-info}/RECORD +215 -163
- {cognee-0.4.0.dist-info → cognee-0.5.0.dist-info}/WHEEL +1 -1
- {cognee-0.4.0.dist-info → cognee-0.5.0.dist-info}/entry_points.txt +0 -1
- cognee/api/v1/cognify/code_graph_pipeline.py +0 -119
- cognee/api/v1/cognify/routers/get_code_pipeline_router.py +0 -90
- cognee/infrastructure/databases/vector/embeddings/embedding_rate_limiter.py +0 -544
- cognee/modules/retrieval/code_retriever.py +0 -232
- cognee/tasks/code/enrich_dependency_graph_checker.py +0 -35
- cognee/tasks/code/get_local_dependencies_checker.py +0 -20
- cognee/tasks/code/get_repo_dependency_graph_checker.py +0 -35
- cognee/tasks/documents/check_permissions_on_dataset.py +0 -26
- cognee/tasks/repo_processor/__init__.py +0 -2
- cognee/tasks/repo_processor/get_local_dependencies.py +0 -335
- cognee/tasks/repo_processor/get_non_code_files.py +0 -158
- cognee/tasks/repo_processor/get_repo_file_dependencies.py +0 -243
- {cognee-0.4.0.dist-info → cognee-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {cognee-0.4.0.dist-info → cognee-0.5.0.dist-info}/licenses/NOTICE.md +0 -0
|
@@ -2,9 +2,7 @@ import asyncio
|
|
|
2
2
|
from typing import Type, List, Optional
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
|
|
5
|
-
from cognee.infrastructure.databases.graph import get_graph_engine
|
|
6
5
|
from cognee.modules.ontology.ontology_env_config import get_ontology_env_config
|
|
7
|
-
from cognee.tasks.storage import index_graph_edges
|
|
8
6
|
from cognee.tasks.storage.add_data_points import add_data_points
|
|
9
7
|
from cognee.modules.ontology.ontology_config import Config
|
|
10
8
|
from cognee.modules.ontology.get_default_ontology_resolver import (
|
|
@@ -25,6 +23,7 @@ from cognee.tasks.graph.exceptions import (
|
|
|
25
23
|
InvalidChunkGraphInputError,
|
|
26
24
|
InvalidOntologyAdapterError,
|
|
27
25
|
)
|
|
26
|
+
from cognee.modules.cognify.config import get_cognify_config
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
async def integrate_chunk_graphs(
|
|
@@ -67,8 +66,6 @@ async def integrate_chunk_graphs(
|
|
|
67
66
|
type(ontology_resolver).__name__ if ontology_resolver else "None"
|
|
68
67
|
)
|
|
69
68
|
|
|
70
|
-
graph_engine = await get_graph_engine()
|
|
71
|
-
|
|
72
69
|
if graph_model is not KnowledgeGraph:
|
|
73
70
|
for chunk_index, chunk_graph in enumerate(chunk_graphs):
|
|
74
71
|
data_chunks[chunk_index].contains = chunk_graph
|
|
@@ -84,12 +81,13 @@ async def integrate_chunk_graphs(
|
|
|
84
81
|
data_chunks, chunk_graphs, ontology_resolver, existing_edges_map
|
|
85
82
|
)
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
cognify_config = get_cognify_config()
|
|
85
|
+
embed_triplets = cognify_config.triplet_embedding
|
|
89
86
|
|
|
90
|
-
if len(
|
|
91
|
-
await
|
|
92
|
-
|
|
87
|
+
if len(graph_nodes) > 0:
|
|
88
|
+
await add_data_points(
|
|
89
|
+
data_points=graph_nodes, custom_edges=graph_edges, embed_triplets=embed_triplets
|
|
90
|
+
)
|
|
93
91
|
|
|
94
92
|
return data_chunks
|
|
95
93
|
|
|
@@ -99,6 +97,7 @@ async def extract_graph_from_data(
|
|
|
99
97
|
graph_model: Type[BaseModel],
|
|
100
98
|
config: Config = None,
|
|
101
99
|
custom_prompt: Optional[str] = None,
|
|
100
|
+
**kwargs,
|
|
102
101
|
) -> List[DocumentChunk]:
|
|
103
102
|
"""
|
|
104
103
|
Extracts and integrates a knowledge graph from the text content of document chunks using a specified graph model.
|
|
@@ -113,7 +112,7 @@ async def extract_graph_from_data(
|
|
|
113
112
|
|
|
114
113
|
chunk_graphs = await asyncio.gather(
|
|
115
114
|
*[
|
|
116
|
-
extract_content_graph(chunk.text, graph_model, custom_prompt=custom_prompt)
|
|
115
|
+
extract_content_graph(chunk.text, graph_model, custom_prompt=custom_prompt, **kwargs)
|
|
117
116
|
for chunk in data_chunks
|
|
118
117
|
]
|
|
119
118
|
)
|
|
@@ -99,7 +99,7 @@ async def ingest_data(
|
|
|
99
99
|
|
|
100
100
|
# data_id is the hash of original file contents + owner id to avoid duplicate data
|
|
101
101
|
|
|
102
|
-
data_id = ingestion.identify(classified_data, user)
|
|
102
|
+
data_id = await ingestion.identify(classified_data, user)
|
|
103
103
|
original_file_metadata = classified_data.get_metadata()
|
|
104
104
|
|
|
105
105
|
# Find metadata from Cognee data storage text file
|
cognee/tasks/memify/__init__.py
CHANGED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import cognee
|
|
2
|
+
|
|
3
|
+
from cognee.exceptions import CogneeValidationError, CogneeSystemError
|
|
4
|
+
from cognee.shared.logging_utils import get_logger
|
|
5
|
+
|
|
6
|
+
logger = get_logger("cognify_session")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
async def cognify_session(data, dataset_id=None):
|
|
10
|
+
"""
|
|
11
|
+
Process and cognify session data into the knowledge graph.
|
|
12
|
+
|
|
13
|
+
Adds session content to cognee with a dedicated "user_sessions" node set,
|
|
14
|
+
then triggers the cognify pipeline to extract entities and relationships
|
|
15
|
+
from the session data.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
data: Session string containing Question, Context, and Answer information.
|
|
19
|
+
dataset_name: Name of dataset.
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
CogneeValidationError: If data is None or empty.
|
|
23
|
+
CogneeSystemError: If cognee operations fail.
|
|
24
|
+
"""
|
|
25
|
+
try:
|
|
26
|
+
if not data or (isinstance(data, str) and not data.strip()):
|
|
27
|
+
logger.warning("Empty session data provided to cognify_session task, skipping")
|
|
28
|
+
raise CogneeValidationError(message="Session data cannot be empty", log=False)
|
|
29
|
+
|
|
30
|
+
logger.info("Processing session data for cognification")
|
|
31
|
+
|
|
32
|
+
await cognee.add(data, dataset_id=dataset_id, node_set=["user_sessions_from_cache"])
|
|
33
|
+
logger.debug("Session data added to cognee with node_set: user_sessions")
|
|
34
|
+
await cognee.cognify(datasets=[dataset_id])
|
|
35
|
+
logger.info("Session data successfully cognified")
|
|
36
|
+
|
|
37
|
+
except CogneeValidationError:
|
|
38
|
+
raise
|
|
39
|
+
except Exception as e:
|
|
40
|
+
logger.error(f"Error cognifying session data: {str(e)}")
|
|
41
|
+
raise CogneeSystemError(message=f"Failed to cognify session data: {str(e)}", log=False)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
|
|
3
|
+
from cognee.context_global_variables import session_user
|
|
4
|
+
from cognee.exceptions import CogneeSystemError
|
|
5
|
+
from cognee.infrastructure.databases.cache.get_cache_engine import get_cache_engine
|
|
6
|
+
from cognee.shared.logging_utils import get_logger
|
|
7
|
+
from cognee.modules.users.models import User
|
|
8
|
+
|
|
9
|
+
logger = get_logger("extract_user_sessions")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def extract_user_sessions(
|
|
13
|
+
data,
|
|
14
|
+
session_ids: Optional[List[str]] = None,
|
|
15
|
+
):
|
|
16
|
+
"""
|
|
17
|
+
Extract Q&A sessions for the current user from cache.
|
|
18
|
+
|
|
19
|
+
Retrieves all Q&A triplets from specified session IDs and yields them
|
|
20
|
+
as formatted strings combining question, context, and answer.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
data: Data passed from memify. If empty dict ({}), no external data is provided.
|
|
24
|
+
session_ids: Optional list of specific session IDs to extract.
|
|
25
|
+
|
|
26
|
+
Yields:
|
|
27
|
+
String containing session ID and all Q&A pairs formatted.
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
CogneeSystemError: If cache engine is unavailable or extraction fails.
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
if not data or data == [{}]:
|
|
34
|
+
logger.info("Fetching session metadata for current user")
|
|
35
|
+
|
|
36
|
+
user: User = session_user.get()
|
|
37
|
+
if not user:
|
|
38
|
+
raise CogneeSystemError(message="No authenticated user found in context", log=False)
|
|
39
|
+
|
|
40
|
+
user_id = str(user.id)
|
|
41
|
+
|
|
42
|
+
cache_engine = get_cache_engine()
|
|
43
|
+
if cache_engine is None:
|
|
44
|
+
raise CogneeSystemError(
|
|
45
|
+
message="Cache engine not available for session extraction, please enable caching in order to have sessions to save",
|
|
46
|
+
log=False,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if session_ids:
|
|
50
|
+
for session_id in session_ids:
|
|
51
|
+
try:
|
|
52
|
+
qa_data = await cache_engine.get_all_qas(user_id, session_id)
|
|
53
|
+
if qa_data:
|
|
54
|
+
logger.info(f"Extracted session {session_id} with {len(qa_data)} Q&A pairs")
|
|
55
|
+
session_string = f"Session ID: {session_id}\n\n"
|
|
56
|
+
for qa_pair in qa_data:
|
|
57
|
+
question = qa_pair.get("question", "")
|
|
58
|
+
answer = qa_pair.get("answer", "")
|
|
59
|
+
session_string += f"Question: {question}\n\nAnswer: {answer}\n\n"
|
|
60
|
+
yield session_string
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.warning(f"Failed to extract session {session_id}: {str(e)}")
|
|
63
|
+
continue
|
|
64
|
+
else:
|
|
65
|
+
logger.info(
|
|
66
|
+
"No specific session_ids provided. Please specify which sessions to extract."
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
except CogneeSystemError:
|
|
70
|
+
raise
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Error extracting user sessions: {str(e)}")
|
|
73
|
+
raise CogneeSystemError(message=f"Failed to extract user sessions: {str(e)}", log=False)
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
from typing import AsyncGenerator, Dict, Any, List, Optional
|
|
2
|
+
from cognee.infrastructure.databases.graph.get_graph_engine import get_graph_engine
|
|
3
|
+
from cognee.modules.engine.utils import generate_node_id
|
|
4
|
+
from cognee.shared.logging_utils import get_logger
|
|
5
|
+
from cognee.modules.graph.utils.convert_node_to_data_point import get_all_subclasses
|
|
6
|
+
from cognee.infrastructure.engine import DataPoint
|
|
7
|
+
from cognee.modules.engine.models import Triplet
|
|
8
|
+
from cognee.tasks.storage import index_data_points
|
|
9
|
+
|
|
10
|
+
logger = get_logger("get_triplet_datapoints")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _build_datapoint_type_index_mapping() -> Dict[str, List[str]]:
|
|
14
|
+
"""
|
|
15
|
+
Build a mapping of DataPoint type names to their index_fields.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
--------
|
|
19
|
+
- Dict[str, List[str]]: Mapping of type name to list of index field names
|
|
20
|
+
"""
|
|
21
|
+
logger.debug("Building DataPoint type to index_fields mapping")
|
|
22
|
+
subclasses = get_all_subclasses(DataPoint)
|
|
23
|
+
datapoint_type_index_property = {}
|
|
24
|
+
|
|
25
|
+
for subclass in subclasses:
|
|
26
|
+
if "metadata" in subclass.model_fields:
|
|
27
|
+
metadata_field = subclass.model_fields["metadata"]
|
|
28
|
+
default = getattr(metadata_field, "default", None)
|
|
29
|
+
if isinstance(default, dict):
|
|
30
|
+
index_fields = default.get("index_fields", [])
|
|
31
|
+
if index_fields:
|
|
32
|
+
datapoint_type_index_property[subclass.__name__] = index_fields
|
|
33
|
+
logger.debug(
|
|
34
|
+
f"Registered {subclass.__name__} with index_fields: {index_fields}"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
logger.info(
|
|
38
|
+
f"Found {len(datapoint_type_index_property)} DataPoint types with index_fields: "
|
|
39
|
+
f"{list(datapoint_type_index_property.keys())}"
|
|
40
|
+
)
|
|
41
|
+
return datapoint_type_index_property
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _extract_embeddable_text(node_or_edge: Dict[str, Any], index_fields: List[str]) -> str:
|
|
45
|
+
"""
|
|
46
|
+
Extract and concatenate embeddable properties from a node or edge dictionary.
|
|
47
|
+
|
|
48
|
+
Parameters:
|
|
49
|
+
-----------
|
|
50
|
+
- node_or_edge (Dict[str, Any]): Dictionary containing node or edge properties.
|
|
51
|
+
- index_fields (List[str]): List of field names to extract and concatenate.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
--------
|
|
55
|
+
- str: Concatenated string of all embeddable property values, or empty string if none found.
|
|
56
|
+
"""
|
|
57
|
+
if not node_or_edge or not index_fields:
|
|
58
|
+
return ""
|
|
59
|
+
|
|
60
|
+
embeddable_values = []
|
|
61
|
+
for field_name in index_fields:
|
|
62
|
+
field_value = node_or_edge.get(field_name)
|
|
63
|
+
if field_value is not None:
|
|
64
|
+
field_value = str(field_value).strip()
|
|
65
|
+
|
|
66
|
+
if field_value:
|
|
67
|
+
embeddable_values.append(field_value)
|
|
68
|
+
|
|
69
|
+
return " ".join(embeddable_values) if embeddable_values else ""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _extract_relationship_text(
|
|
73
|
+
relationship: Dict[str, Any], datapoint_type_index_property: Dict[str, List[str]]
|
|
74
|
+
) -> str:
|
|
75
|
+
"""
|
|
76
|
+
Extract relationship text from edge properties.
|
|
77
|
+
|
|
78
|
+
Parameters:
|
|
79
|
+
-----------
|
|
80
|
+
- relationship (Dict[str, Any]): Dictionary containing relationship properties
|
|
81
|
+
- datapoint_type_index_property (Dict[str, List[str]]): Mapping of type to index fields
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
--------
|
|
85
|
+
- str: Extracted relationship text or empty string
|
|
86
|
+
"""
|
|
87
|
+
if not relationship:
|
|
88
|
+
return ""
|
|
89
|
+
|
|
90
|
+
edge_text = relationship.get("edge_text")
|
|
91
|
+
if edge_text and isinstance(edge_text, str) and edge_text.strip():
|
|
92
|
+
return edge_text.strip()
|
|
93
|
+
|
|
94
|
+
# Fallback to extracting from EdgeType index_fields
|
|
95
|
+
edge_type_index_fields = datapoint_type_index_property.get("EdgeType", [])
|
|
96
|
+
return _extract_embeddable_text(relationship, edge_type_index_fields)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _process_single_triplet(
|
|
100
|
+
triplet_datapoint: Dict[str, Any],
|
|
101
|
+
datapoint_type_index_property: Dict[str, List[str]],
|
|
102
|
+
offset: int,
|
|
103
|
+
idx: int,
|
|
104
|
+
) -> tuple[Optional[Triplet], Optional[str]]:
|
|
105
|
+
"""
|
|
106
|
+
Process a single triplet and create a Triplet object.
|
|
107
|
+
|
|
108
|
+
Parameters:
|
|
109
|
+
-----------
|
|
110
|
+
- triplet_datapoint (Dict[str, Any]): Raw triplet data from graph engine
|
|
111
|
+
- datapoint_type_index_property (Dict[str, List[str]]): Type to index fields mapping
|
|
112
|
+
- offset (int): Current batch offset
|
|
113
|
+
- idx (int): Index within current batch
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
--------
|
|
117
|
+
- tuple[Optional[Triplet], Optional[str]]: (Triplet object, error message if skipped)
|
|
118
|
+
"""
|
|
119
|
+
start_node = triplet_datapoint.get("start_node", {})
|
|
120
|
+
end_node = triplet_datapoint.get("end_node", {})
|
|
121
|
+
relationship = triplet_datapoint.get("relationship_properties", {})
|
|
122
|
+
|
|
123
|
+
start_node_type = start_node.get("type")
|
|
124
|
+
end_node_type = end_node.get("type")
|
|
125
|
+
|
|
126
|
+
start_index_fields = datapoint_type_index_property.get(start_node_type, [])
|
|
127
|
+
end_index_fields = datapoint_type_index_property.get(end_node_type, [])
|
|
128
|
+
|
|
129
|
+
if not start_index_fields:
|
|
130
|
+
logger.debug(
|
|
131
|
+
f"No index_fields found for start_node type '{start_node_type}' in triplet {offset + idx}"
|
|
132
|
+
)
|
|
133
|
+
if not end_index_fields:
|
|
134
|
+
logger.debug(
|
|
135
|
+
f"No index_fields found for end_node type '{end_node_type}' in triplet {offset + idx}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
start_node_id = start_node.get("id", "")
|
|
139
|
+
end_node_id = end_node.get("id", "")
|
|
140
|
+
|
|
141
|
+
if not start_node_id or not end_node_id:
|
|
142
|
+
return None, (
|
|
143
|
+
f"Skipping triplet at offset {offset + idx}: missing node IDs "
|
|
144
|
+
f"(start: {start_node_id}, end: {end_node_id})"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
relationship_text = _extract_relationship_text(relationship, datapoint_type_index_property)
|
|
148
|
+
start_node_text = _extract_embeddable_text(start_node, start_index_fields)
|
|
149
|
+
end_node_text = _extract_embeddable_text(end_node, end_index_fields)
|
|
150
|
+
|
|
151
|
+
if not start_node_text and not end_node_text and not relationship_text:
|
|
152
|
+
return None, (
|
|
153
|
+
f"Skipping triplet at offset {offset + idx}: empty embeddable text "
|
|
154
|
+
f"(start_node_id: {start_node_id}, end_node_id: {end_node_id})"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
embeddable_text = f"{start_node_text}-›{relationship_text}-›{end_node_text}".strip()
|
|
158
|
+
|
|
159
|
+
relationship_name = relationship.get("relationship_name", "")
|
|
160
|
+
triplet_id = generate_node_id(str(start_node_id) + str(relationship_name) + str(end_node_id))
|
|
161
|
+
|
|
162
|
+
triplet_obj = Triplet(
|
|
163
|
+
id=triplet_id, from_node_id=start_node_id, to_node_id=end_node_id, text=embeddable_text
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return triplet_obj, None
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def get_triplet_datapoints(
|
|
170
|
+
data,
|
|
171
|
+
triplets_batch_size: int = 100,
|
|
172
|
+
) -> AsyncGenerator[Triplet, None]:
|
|
173
|
+
"""
|
|
174
|
+
Async generator that yields batches of triplet datapoints with embeddable text extracted.
|
|
175
|
+
|
|
176
|
+
Each triplet in the batch includes:
|
|
177
|
+
- Original triplet structure (start_node, relationship_properties, end_node)
|
|
178
|
+
- Extracted embeddable text for each element based on index_fields
|
|
179
|
+
|
|
180
|
+
Parameters:
|
|
181
|
+
-----------
|
|
182
|
+
- triplets_batch_size (int): Number of triplets to retrieve per batch. Default is 100.
|
|
183
|
+
|
|
184
|
+
Yields:
|
|
185
|
+
-------
|
|
186
|
+
- List[Dict[str, Any]]: A batch of triplets, each enriched with embeddable text.
|
|
187
|
+
"""
|
|
188
|
+
if not data or data == [{}]:
|
|
189
|
+
logger.info("Fetching graph data for current user")
|
|
190
|
+
|
|
191
|
+
logger.info(f"Starting triplet datapoints extraction with batch size: {triplets_batch_size}")
|
|
192
|
+
|
|
193
|
+
graph_engine = await get_graph_engine()
|
|
194
|
+
graph_engine_type = type(graph_engine).__name__
|
|
195
|
+
logger.debug(f"Using graph engine: {graph_engine_type}")
|
|
196
|
+
|
|
197
|
+
if not hasattr(graph_engine, "get_triplets_batch"):
|
|
198
|
+
error_msg = f"Graph adapter {graph_engine_type} does not support get_triplets_batch method"
|
|
199
|
+
logger.error(error_msg)
|
|
200
|
+
raise NotImplementedError(error_msg)
|
|
201
|
+
|
|
202
|
+
datapoint_type_index_property = _build_datapoint_type_index_mapping()
|
|
203
|
+
|
|
204
|
+
offset = 0
|
|
205
|
+
total_triplets_processed = 0
|
|
206
|
+
batch_number = 0
|
|
207
|
+
|
|
208
|
+
while True:
|
|
209
|
+
try:
|
|
210
|
+
batch_number += 1
|
|
211
|
+
logger.debug(
|
|
212
|
+
f"Fetching triplet batch {batch_number} (offset: {offset}, limit: {triplets_batch_size})"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
triplets_batch = await graph_engine.get_triplets_batch(
|
|
216
|
+
offset=offset, limit=triplets_batch_size
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if not triplets_batch:
|
|
220
|
+
logger.info(f"No more triplets found at offset {offset}. Processing complete.")
|
|
221
|
+
break
|
|
222
|
+
|
|
223
|
+
logger.debug(f"Retrieved {len(triplets_batch)} triplets in batch {batch_number}")
|
|
224
|
+
|
|
225
|
+
triplet_datapoints = []
|
|
226
|
+
skipped_count = 0
|
|
227
|
+
|
|
228
|
+
for idx, triplet_datapoint in enumerate(triplets_batch):
|
|
229
|
+
try:
|
|
230
|
+
triplet_obj, error_msg = _process_single_triplet(
|
|
231
|
+
triplet_datapoint, datapoint_type_index_property, offset, idx
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
if error_msg:
|
|
235
|
+
logger.warning(error_msg)
|
|
236
|
+
skipped_count += 1
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
if triplet_obj:
|
|
240
|
+
triplet_datapoints.append(triplet_obj)
|
|
241
|
+
yield triplet_obj
|
|
242
|
+
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.warning(
|
|
245
|
+
f"Error processing triplet at offset {offset + idx}: {e}. "
|
|
246
|
+
f"Skipping this triplet and continuing."
|
|
247
|
+
)
|
|
248
|
+
skipped_count += 1
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
if skipped_count > 0:
|
|
252
|
+
logger.warning(
|
|
253
|
+
f"Skipped {skipped_count} out of {len(triplets_batch)} triplets in batch {batch_number}"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
if not triplet_datapoints:
|
|
257
|
+
logger.warning(
|
|
258
|
+
f"No valid triplet datapoints in batch {batch_number} after processing"
|
|
259
|
+
)
|
|
260
|
+
offset += len(triplets_batch)
|
|
261
|
+
if len(triplets_batch) < triplets_batch_size:
|
|
262
|
+
break
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
total_triplets_processed += len(triplet_datapoints)
|
|
266
|
+
logger.info(
|
|
267
|
+
f"Batch {batch_number} complete: processed {len(triplet_datapoints)} triplets "
|
|
268
|
+
f"(total processed: {total_triplets_processed})"
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
offset += len(triplets_batch)
|
|
272
|
+
if len(triplets_batch) < triplets_batch_size:
|
|
273
|
+
logger.info(
|
|
274
|
+
f"Last batch retrieved (got {len(triplets_batch)} < {triplets_batch_size} triplets). "
|
|
275
|
+
f"Processing complete."
|
|
276
|
+
)
|
|
277
|
+
break
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
logger.error(
|
|
281
|
+
f"Error retrieving triplet batch {batch_number} at offset {offset}: {e}",
|
|
282
|
+
exc_info=True,
|
|
283
|
+
)
|
|
284
|
+
raise
|
|
285
|
+
|
|
286
|
+
logger.info(
|
|
287
|
+
f"Triplet datapoints extraction complete. "
|
|
288
|
+
f"Processed {total_triplets_processed} triplets across {batch_number} batch(es)."
|
|
289
|
+
)
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import List
|
|
2
|
+
from typing import List, Dict, Optional
|
|
3
3
|
from cognee.infrastructure.engine import DataPoint
|
|
4
4
|
from cognee.infrastructure.databases.graph import get_graph_engine
|
|
5
5
|
from cognee.modules.graph.utils import deduplicate_nodes_and_edges, get_graph_from_model
|
|
6
6
|
from .index_data_points import index_data_points
|
|
7
7
|
from .index_graph_edges import index_graph_edges
|
|
8
|
+
from cognee.modules.engine.models import Triplet
|
|
9
|
+
from cognee.shared.logging_utils import get_logger
|
|
8
10
|
from cognee.tasks.storage.exceptions import (
|
|
9
11
|
InvalidDataPointsInAddDataPointsError,
|
|
10
12
|
)
|
|
13
|
+
from ...modules.engine.utils import generate_node_id
|
|
11
14
|
|
|
15
|
+
logger = get_logger("add_data_points")
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
|
|
18
|
+
async def add_data_points(
|
|
19
|
+
data_points: List[DataPoint], custom_edges: Optional[List] = None, embed_triplets: bool = False
|
|
20
|
+
) -> List[DataPoint]:
|
|
14
21
|
"""
|
|
15
22
|
Add a batch of data points to the graph database by extracting nodes and edges,
|
|
16
23
|
deduplicating them, and indexing them for retrieval.
|
|
@@ -23,6 +30,10 @@ async def add_data_points(data_points: List[DataPoint]) -> List[DataPoint]:
|
|
|
23
30
|
Args:
|
|
24
31
|
data_points (List[DataPoint]):
|
|
25
32
|
A list of data points to process and insert into the graph.
|
|
33
|
+
custom_edges (List[tuple]): Custom edges between datapoints.
|
|
34
|
+
embed_triplets (bool):
|
|
35
|
+
If True, creates and indexes triplet embeddings from the graph structure.
|
|
36
|
+
Defaults to False.
|
|
26
37
|
|
|
27
38
|
Returns:
|
|
28
39
|
List[DataPoint]:
|
|
@@ -34,6 +45,7 @@ async def add_data_points(data_points: List[DataPoint]) -> List[DataPoint]:
|
|
|
34
45
|
- Updates the node index via `index_data_points`.
|
|
35
46
|
- Inserts nodes and edges into the graph engine.
|
|
36
47
|
- Optionally updates the edge index via `index_graph_edges`.
|
|
48
|
+
- Optionally creates and indexes triplet embeddings if embed_triplets is True.
|
|
37
49
|
"""
|
|
38
50
|
|
|
39
51
|
if not isinstance(data_points, list):
|
|
@@ -74,4 +86,132 @@ async def add_data_points(data_points: List[DataPoint]) -> List[DataPoint]:
|
|
|
74
86
|
await graph_engine.add_edges(edges)
|
|
75
87
|
await index_graph_edges(edges)
|
|
76
88
|
|
|
89
|
+
if isinstance(custom_edges, list) and custom_edges:
|
|
90
|
+
# This must be handled separately from datapoint edges, created a task in linear to dig deeper but (COG-3488)
|
|
91
|
+
await graph_engine.add_edges(custom_edges)
|
|
92
|
+
await index_graph_edges(custom_edges)
|
|
93
|
+
edges.extend(custom_edges)
|
|
94
|
+
|
|
95
|
+
if embed_triplets:
|
|
96
|
+
triplets = _create_triplets_from_graph(nodes, edges)
|
|
97
|
+
if triplets:
|
|
98
|
+
await index_data_points(triplets)
|
|
99
|
+
logger.info(f"Created and indexed {len(triplets)} triplets from graph structure")
|
|
100
|
+
|
|
77
101
|
return data_points
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _extract_embeddable_text_from_datapoint(data_point: DataPoint) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Extract embeddable text from a DataPoint using its index_fields metadata.
|
|
107
|
+
Uses the same approach as index_data_points.
|
|
108
|
+
|
|
109
|
+
Parameters:
|
|
110
|
+
-----------
|
|
111
|
+
- data_point (DataPoint): The data point to extract text from.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
--------
|
|
115
|
+
- str: Concatenated string of all embeddable property values, or empty string if none found.
|
|
116
|
+
"""
|
|
117
|
+
if not data_point or not hasattr(data_point, "metadata"):
|
|
118
|
+
return ""
|
|
119
|
+
|
|
120
|
+
index_fields = data_point.metadata.get("index_fields", [])
|
|
121
|
+
if not index_fields:
|
|
122
|
+
return ""
|
|
123
|
+
|
|
124
|
+
embeddable_values = []
|
|
125
|
+
for field_name in index_fields:
|
|
126
|
+
field_value = getattr(data_point, field_name, None)
|
|
127
|
+
if field_value is not None:
|
|
128
|
+
field_value = str(field_value).strip()
|
|
129
|
+
|
|
130
|
+
if field_value:
|
|
131
|
+
embeddable_values.append(field_value)
|
|
132
|
+
|
|
133
|
+
return " ".join(embeddable_values) if embeddable_values else ""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _create_triplets_from_graph(nodes: List[DataPoint], edges: List[tuple]) -> List[Triplet]:
|
|
137
|
+
"""
|
|
138
|
+
Create Triplet objects from graph nodes and edges.
|
|
139
|
+
|
|
140
|
+
This function processes graph edges and their corresponding nodes to create
|
|
141
|
+
triplet datapoints with embeddable text, similar to the triplet embeddings pipeline.
|
|
142
|
+
|
|
143
|
+
Parameters:
|
|
144
|
+
-----------
|
|
145
|
+
- nodes (List[DataPoint]): List of graph nodes extracted from data points
|
|
146
|
+
- edges (List[tuple]): List of edge tuples in format
|
|
147
|
+
(source_node_id, target_node_id, relationship_name, properties_dict)
|
|
148
|
+
Note: All edges including those from DocumentChunk.contains are already extracted
|
|
149
|
+
by get_graph_from_model and included in this list.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
--------
|
|
153
|
+
- List[Triplet]: List of Triplet objects ready for indexing
|
|
154
|
+
"""
|
|
155
|
+
node_map: Dict[str, DataPoint] = {}
|
|
156
|
+
for node in nodes:
|
|
157
|
+
if hasattr(node, "id"):
|
|
158
|
+
node_id = str(node.id)
|
|
159
|
+
if node_id not in node_map:
|
|
160
|
+
node_map[node_id] = node
|
|
161
|
+
|
|
162
|
+
triplets = []
|
|
163
|
+
skipped_count = 0
|
|
164
|
+
seen_ids = set()
|
|
165
|
+
|
|
166
|
+
for edge_tuple in edges:
|
|
167
|
+
if len(edge_tuple) < 4:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
source_node_id, target_node_id, relationship_name, edge_properties = (
|
|
171
|
+
edge_tuple[0],
|
|
172
|
+
edge_tuple[1],
|
|
173
|
+
edge_tuple[2],
|
|
174
|
+
edge_tuple[3],
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
source_node = node_map.get(str(source_node_id))
|
|
178
|
+
target_node = node_map.get(str(target_node_id))
|
|
179
|
+
|
|
180
|
+
if not source_node or not target_node or relationship_name is None:
|
|
181
|
+
skipped_count += 1
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
source_node_text = _extract_embeddable_text_from_datapoint(source_node)
|
|
185
|
+
target_node_text = _extract_embeddable_text_from_datapoint(target_node)
|
|
186
|
+
|
|
187
|
+
relationship_text = ""
|
|
188
|
+
if isinstance(edge_properties, dict):
|
|
189
|
+
edge_text = edge_properties.get("edge_text")
|
|
190
|
+
if edge_text and isinstance(edge_text, str) and edge_text.strip():
|
|
191
|
+
relationship_text = edge_text.strip()
|
|
192
|
+
|
|
193
|
+
if not relationship_text and relationship_name:
|
|
194
|
+
relationship_text = relationship_name
|
|
195
|
+
|
|
196
|
+
if not source_node_text and not relationship_text and not relationship_name:
|
|
197
|
+
skipped_count += 1
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
embeddable_text = f"{source_node_text} -› {relationship_text}-›{target_node_text}".strip()
|
|
201
|
+
|
|
202
|
+
triplet_id = generate_node_id(str(source_node_id) + relationship_name + str(target_node_id))
|
|
203
|
+
|
|
204
|
+
if triplet_id in seen_ids:
|
|
205
|
+
continue
|
|
206
|
+
seen_ids.add(triplet_id)
|
|
207
|
+
|
|
208
|
+
triplets.append(
|
|
209
|
+
Triplet(
|
|
210
|
+
id=triplet_id,
|
|
211
|
+
from_node_id=str(source_node_id),
|
|
212
|
+
to_node_id=str(target_node_id),
|
|
213
|
+
text=embeddable_text,
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
return triplets
|