Files
wehub-resource-sync c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:02:24 +08:00

231 lines
8.6 KiB
Python

import asyncio
import inspect
from typing import Type, List, Optional
from pydantic import BaseModel
from cognee.modules.pipelines.tasks.task import task_summary
from cognee.modules.ontology.ontology_env_config import get_ontology_env_config
from cognee.modules.ontology.ontology_config import Config
from cognee.modules.ontology.get_default_ontology_resolver import (
get_default_ontology_resolver,
get_ontology_resolver_from_env,
)
from cognee.modules.ontology.base_ontology_resolver import BaseOntologyResolver
from cognee.modules.chunking.models.DocumentChunk import DocumentChunk
from cognee.modules.graph.utils import (
expand_with_nodes_and_edges,
retrieve_existing_edges,
)
from cognee.shared.data_models import KnowledgeGraph
from cognee.infrastructure.llm.extraction import extract_content_graph
from cognee.infrastructure.llm.pipeline_stage import pipeline_stage
from cognee.infrastructure.engine import DataPoint
from cognee.tasks.graph.exceptions import (
InvalidGraphModelError,
InvalidDataChunksError,
InvalidChunkGraphInputError,
InvalidOntologyAdapterError,
)
def _stamp_provenance_deep(data, pipeline_name, task_name, visited=None):
"""Recursively stamp all reachable DataPoints with provenance info."""
if visited is None:
visited = set()
if isinstance(data, DataPoint):
obj_id = id(data)
if obj_id in visited:
return
visited.add(obj_id)
if data.source_pipeline is None:
data.source_pipeline = pipeline_name
if data.source_task is None:
data.source_task = task_name
for field_name in data.model_fields:
field_value = getattr(data, field_name, None)
if field_value is not None:
_stamp_provenance_deep(field_value, pipeline_name, task_name, visited)
elif isinstance(data, (list, tuple)):
for item in data:
_stamp_provenance_deep(item, pipeline_name, task_name, visited)
async def integrate_chunk_graphs(
data_chunks: list[DocumentChunk],
chunk_graphs: list,
graph_model: Type[BaseModel],
ontology_resolver: BaseOntologyResolver,
pipeline_name: str = None,
task_name: str = None,
**kwargs,
) -> List[DocumentChunk]:
"""Integrate chunk graphs with ontology validation and store in databases.
This function processes document chunks and their associated knowledge graphs,
validates entities against an ontology resolver, and stores the integrated
data points and edges in the configured databases.
Args:
data_chunks: List of document chunks containing source data
chunk_graphs: List of knowledge graphs corresponding to each chunk
graph_model: Pydantic model class for graph data validation
ontology_resolver: Resolver for validating entities against ontology
Returns:
List of updated DocumentChunk objects with integrated data
Raises:
InvalidChunkGraphInputError: If input validation fails
InvalidGraphModelError: If graph model validation fails
InvalidOntologyAdapterError: If ontology resolver validation fails
"""
if not isinstance(data_chunks, list) or not isinstance(chunk_graphs, list):
raise InvalidChunkGraphInputError("data_chunks and chunk_graphs must be lists.")
if len(data_chunks) != len(chunk_graphs):
raise InvalidChunkGraphInputError(
f"length mismatch: {len(data_chunks)} chunks vs {len(chunk_graphs)} graphs."
)
if not isinstance(graph_model, type) or not issubclass(graph_model, BaseModel):
raise InvalidGraphModelError(graph_model)
if ontology_resolver is None or not hasattr(ontology_resolver, "get_subgraph"):
raise InvalidOntologyAdapterError(
type(ontology_resolver).__name__ if ontology_resolver else "None"
)
if not issubclass(graph_model, KnowledgeGraph):
for chunk_index, chunk_graph in enumerate(chunk_graphs):
data_chunks[chunk_index].contains = chunk_graph
return data_chunks
existing_edges_map = await retrieve_existing_edges(
data_chunks,
chunk_graphs,
)
data_chunks, entity_nodes = expand_with_nodes_and_edges(
data_chunks, chunk_graphs, ontology_resolver, existing_edges_map
)
if entity_nodes:
if pipeline_name or task_name:
for node in entity_nodes:
_stamp_provenance_deep(node, pipeline_name, task_name)
cache_entity_embeddings = kwargs.get("cache_entity_embeddings")
if callable(cache_entity_embeddings):
callback_result = cache_entity_embeddings(entity_nodes, **kwargs)
if inspect.isawaitable(callback_result):
await callback_result
return data_chunks
@task_summary("Extracted graph from {n} chunk(s)")
async def extract_graph_from_data(
data_chunks: List[DocumentChunk],
graph_model: Type[BaseModel],
config: Optional[Config] = None,
custom_prompt: Optional[str] = None,
ctx=None,
**kwargs,
) -> List[DocumentChunk]:
"""
Extracts and integrates a knowledge graph from the text content of document chunks using a specified graph model.
"""
pipeline_name = ctx.pipeline_name if ctx else None
if not isinstance(data_chunks, list) or not data_chunks:
raise InvalidDataChunksError("must be a non-empty list of DocumentChunk.")
if not all(hasattr(c, "text") for c in data_chunks):
raise InvalidDataChunksError("each chunk must have a 'text' attribute")
if not isinstance(graph_model, type) or not issubclass(graph_model, BaseModel):
raise InvalidGraphModelError(graph_model)
# Skip LLM extraction for DLT row chunks — their graph is built
# deterministically by extract_dlt_fk_edges from schema metadata.
from cognee.modules.data.processing.document_types import DltRowDocument
# Partition in a single pass: a list-membership check against dlt_chunks
# rescans the list for every chunk (O(n^2) with Pydantic __eq__ comparisons),
# which becomes a bottleneck on the extraction hot path for large DLT sources.
dlt_chunks = []
non_dlt_chunks = []
for c in data_chunks:
if isinstance(getattr(c, "is_part_of", None), DltRowDocument):
dlt_chunks.append(c)
else:
non_dlt_chunks.append(c)
if not non_dlt_chunks:
return data_chunks
calculate_chunk_graphs = kwargs.get("calculate_chunk_graphs")
if callable(calculate_chunk_graphs):
extracted = calculate_chunk_graphs(non_dlt_chunks, graph_model, custom_prompt, **kwargs)
chunk_graphs = await extracted if inspect.isawaitable(extracted) else extracted
else:
with pipeline_stage("extraction"):
chunk_graphs = await asyncio.gather(
*[
extract_content_graph(
chunk.text, graph_model, custom_prompt=custom_prompt, **kwargs
)
for chunk in non_dlt_chunks
]
)
cache_entity_embeddings = kwargs.get("cache_entity_embeddings")
if callable(cache_entity_embeddings):
callback_result = cache_entity_embeddings(chunk_graphs, **kwargs)
if inspect.isawaitable(callback_result):
await callback_result
# Note: Filter edges with missing source or target nodes
if issubclass(graph_model, KnowledgeGraph):
for graph in chunk_graphs:
valid_node_ids = {node.id for node in graph.nodes}
graph.edges = [
edge
for edge in graph.edges
if edge.source_node_id in valid_node_ids and edge.target_node_id in valid_node_ids
]
# Extract resolver from config if provided, otherwise get default
if config is None:
ontology_config = get_ontology_env_config()
if (
ontology_config.ontology_file_path
and ontology_config.ontology_resolver
and ontology_config.matching_strategy
):
config: Config = {
"ontology_config": {
"ontology_resolver": get_ontology_resolver_from_env(**ontology_config.to_dict())
}
}
else:
config: Config = {
"ontology_config": {"ontology_resolver": get_default_ontology_resolver()}
}
ontology_resolver = config["ontology_config"]["ontology_resolver"]
task_name = "extract_graph_from_data"
integrated = await integrate_chunk_graphs(
non_dlt_chunks,
chunk_graphs,
graph_model,
ontology_resolver,
pipeline_name=pipeline_name,
task_name=task_name,
**kwargs,
)
return integrated + dlt_chunks