From 6bb0f3d8f2a306daabc3384c2d0c38cc8f3cb60f Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Wed, 4 Dec 2024 15:53:25 +0100 Subject: [PATCH 01/10] feat: Add ability to store single data instance in multiple datasets Added ability to store single data instance in multiple datasets Feature COG-505 --- cognee/tasks/ingestion/ingest_data_with_metadata.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cognee/tasks/ingestion/ingest_data_with_metadata.py b/cognee/tasks/ingestion/ingest_data_with_metadata.py index 0a238b548..7c0b840eb 100644 --- a/cognee/tasks/ingestion/ingest_data_with_metadata.py +++ b/cognee/tasks/ingestion/ingest_data_with_metadata.py @@ -4,6 +4,7 @@ import cognee.modules.ingestion as ingestion from cognee.infrastructure.databases.relational import get_relational_engine from cognee.modules.data.methods import create_dataset +from cognee.modules.data.models.DatasetData import DatasetData from cognee.modules.data.operations.delete_metadata import delete_metadata from cognee.modules.users.models import User from cognee.modules.users.permissions.methods import give_permission_on_document @@ -89,7 +90,17 @@ async def data_storing(data: Any, dataset_name: str, user: User): mime_type = file_metadata["mime_type"] ) + + + # Check if data is already in dataset + dataset_data = ( + await session.execute(select(DatasetData).filter(DatasetData.data_id == data_id, + DatasetData.dataset_id == dataset.id)) + ).scalar_one_or_none() + # If data is not present in dataset add it + if dataset_data is None: dataset.data.append(data_point) + await session.commit() await write_metadata(data_item, data_point.id, file_metadata) From 0ce254b262cdc50efac394748de5631b69edcc0f Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Wed, 4 Dec 2024 17:19:29 +0100 Subject: [PATCH 02/10] feat: Add text deduplication If text is added to cognee it will be saved by hash so the same text can't be stored multiple times Feature COG-505 --- cognee/api/v1/add/add.py | 8 ++++---- cognee/modules/ingestion/save_data_to_file.py | 19 +++++++++++-------- .../ingestion/ingest_data_with_metadata.py | 2 -- .../ingestion/save_data_item_to_storage.py | 4 ++-- ...save_data_item_with_metadata_to_storage.py | 4 ++-- cognee/tasks/ingestion/transform_data.py | 4 ++-- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/cognee/api/v1/add/add.py b/cognee/api/v1/add/add.py index 10430ed8d..39ee01964 100644 --- a/cognee/api/v1/add/add.py +++ b/cognee/api/v1/add/add.py @@ -33,11 +33,11 @@ async def add(data: Union[BinaryIO, List[BinaryIO], str, List[str]], dataset_nam # data is text else: - file_path = save_data_to_file(data, dataset_name) + file_path = save_data_to_file(data) return await add([file_path], dataset_name) if hasattr(data, "file"): - file_path = save_data_to_file(data.file, dataset_name, filename = data.filename) + file_path = save_data_to_file(data.file, filename = data.filename) return await add([file_path], dataset_name) # data is a list of file paths or texts @@ -45,13 +45,13 @@ async def add(data: Union[BinaryIO, List[BinaryIO], str, List[str]], dataset_nam for data_item in data: if hasattr(data_item, "file"): - file_paths.append(save_data_to_file(data_item, dataset_name, filename = data_item.filename)) + file_paths.append(save_data_to_file(data_item, filename = data_item.filename)) elif isinstance(data_item, str) and ( data_item.startswith("/") or data_item.startswith("file://") ): file_paths.append(data_item) elif isinstance(data_item, str): - file_paths.append(save_data_to_file(data_item, dataset_name)) + file_paths.append(save_data_to_file(data_item)) if len(file_paths) > 0: return await add_files(file_paths, dataset_name, user) diff --git a/cognee/modules/ingestion/save_data_to_file.py b/cognee/modules/ingestion/save_data_to_file.py index 1bbfaec37..1af6ab0aa 100644 --- a/cognee/modules/ingestion/save_data_to_file.py +++ b/cognee/modules/ingestion/save_data_to_file.py @@ -1,25 +1,28 @@ -import string -import random +import os.path +import hashlib from typing import BinaryIO, Union from cognee.base_config import get_base_config from cognee.infrastructure.files.storage import LocalStorage from .classify import classify -def save_data_to_file(data: Union[str, BinaryIO], dataset_name: str, filename: str = None): +def save_data_to_file(data: Union[str, BinaryIO], filename: str = None): base_config = get_base_config() data_directory_path = base_config.data_root_directory classified_data = classify(data, filename) - storage_path = data_directory_path + "/" + dataset_name.replace(".", "/") + storage_path = os.path.join(data_directory_path, "data") LocalStorage.ensure_directory_exists(storage_path) file_metadata = classified_data.get_metadata() if "name" not in file_metadata or file_metadata["name"] is None: - letters = string.ascii_lowercase - random_string = "".join(random.choice(letters) for _ in range(32)) - file_metadata["name"] = "text_" + random_string + ".txt" + data_contents = classified_data.get_data().encode('utf-8') + hash_contents = hashlib.md5(data_contents).hexdigest() + file_metadata["name"] = "text_" + hash_contents + ".txt" file_name = file_metadata["name"] - LocalStorage(storage_path).store(file_name, classified_data.get_data()) + + # Don't save file if it already exists + if not os.path.isfile(os.path.join(storage_path, file_name)): + LocalStorage(storage_path).store(file_name, classified_data.get_data()) return "file://" + storage_path + "/" + file_name diff --git a/cognee/tasks/ingestion/ingest_data_with_metadata.py b/cognee/tasks/ingestion/ingest_data_with_metadata.py index 7c0b840eb..59e4a16f3 100644 --- a/cognee/tasks/ingestion/ingest_data_with_metadata.py +++ b/cognee/tasks/ingestion/ingest_data_with_metadata.py @@ -90,8 +90,6 @@ async def data_storing(data: Any, dataset_name: str, user: User): mime_type = file_metadata["mime_type"] ) - - # Check if data is already in dataset dataset_data = ( await session.execute(select(DatasetData).filter(DatasetData.data_id == data_id, diff --git a/cognee/tasks/ingestion/save_data_item_to_storage.py b/cognee/tasks/ingestion/save_data_item_to_storage.py index e2a7c8ee7..88d499e74 100644 --- a/cognee/tasks/ingestion/save_data_item_to_storage.py +++ b/cognee/tasks/ingestion/save_data_item_to_storage.py @@ -7,7 +7,7 @@ def save_data_item_to_storage(data_item: Union[BinaryIO, str], dataset_name: str # data is a file object coming from upload. if hasattr(data_item, "file"): - file_path = save_data_to_file(data_item.file, dataset_name, filename=data_item.filename) + file_path = save_data_to_file(data_item.file, filename=data_item.filename) elif isinstance(data_item, str): # data is a file path @@ -15,7 +15,7 @@ def save_data_item_to_storage(data_item: Union[BinaryIO, str], dataset_name: str file_path = data_item.replace("file://", "") # data is text else: - file_path = save_data_to_file(data_item, dataset_name) + file_path = save_data_to_file(data_item) else: raise IngestionError(message=f"Data type not supported: {type(data_item)}") diff --git a/cognee/tasks/ingestion/save_data_item_with_metadata_to_storage.py b/cognee/tasks/ingestion/save_data_item_with_metadata_to_storage.py index d758ebcd1..06dde11bd 100644 --- a/cognee/tasks/ingestion/save_data_item_with_metadata_to_storage.py +++ b/cognee/tasks/ingestion/save_data_item_with_metadata_to_storage.py @@ -17,7 +17,7 @@ async def save_data_item_with_metadata_to_storage( # data is a file object coming from upload. elif hasattr(data_item, "file"): file_path = save_data_to_file( - data_item.file, dataset_name, filename=data_item.filename + data_item.file, filename=data_item.filename ) elif isinstance(data_item, str): @@ -26,7 +26,7 @@ async def save_data_item_with_metadata_to_storage( file_path = data_item.replace("file://", "") # data is text else: - file_path = save_data_to_file(data_item, dataset_name) + file_path = save_data_to_file(data_item) else: raise IngestionError(message=f"Data type not supported: {type(data_item)}") diff --git a/cognee/tasks/ingestion/transform_data.py b/cognee/tasks/ingestion/transform_data.py index c2ea86c47..898ac6e71 100644 --- a/cognee/tasks/ingestion/transform_data.py +++ b/cognee/tasks/ingestion/transform_data.py @@ -8,11 +8,11 @@ def get_data_from_llama_index(data_point: Union[Document, ImageDocument], datase if type(data_point) == Document: file_path = data_point.metadata.get("file_path") if file_path is None: - file_path = save_data_to_file(data_point.text, dataset_name) + file_path = save_data_to_file(data_point.text) return file_path return file_path elif type(data_point) == ImageDocument: if data_point.image_path is None: - file_path = save_data_to_file(data_point.text, dataset_name) + file_path = save_data_to_file(data_point.text) return file_path return data_point.image_path \ No newline at end of file From f5b5e56cc1d68e340c24ab12ee580cca2c5fcca0 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 5 Dec 2024 16:38:44 +0100 Subject: [PATCH 03/10] feat: Add deduplication of data Data is deduplicated per user so if a user tries to add data which already exists it will just be redirected to existing data in database Feature COG-505 --- .../files/utils/get_file_metadata.py | 6 ++++++ cognee/modules/data/models/Data.py | 3 ++- .../modules/data/operations/write_metadata.py | 1 - .../ingestion/data_types/BinaryData.py | 2 +- cognee/modules/ingestion/identify.py | 8 ++++--- .../ingestion/ingest_data_with_metadata.py | 21 ++++++++++++------- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/cognee/infrastructure/files/utils/get_file_metadata.py b/cognee/infrastructure/files/utils/get_file_metadata.py index a114ef48f..a9528e5c0 100644 --- a/cognee/infrastructure/files/utils/get_file_metadata.py +++ b/cognee/infrastructure/files/utils/get_file_metadata.py @@ -1,4 +1,5 @@ from typing import BinaryIO, TypedDict +import hashlib from .guess_file_type import guess_file_type @@ -7,10 +8,14 @@ class FileMetadata(TypedDict): file_path: str mime_type: str extension: str + content_hash: str def get_file_metadata(file: BinaryIO) -> FileMetadata: """Get metadata from a file""" file.seek(0) + content_hash = hashlib.md5(file.read()).hexdigest() + file.seek(0) + file_type = guess_file_type(file) file_path = file.name @@ -21,4 +26,5 @@ def get_file_metadata(file: BinaryIO) -> FileMetadata: file_path = file_path, mime_type = file_type.mime, extension = file_type.extension, + content_hash = content_hash, ) diff --git a/cognee/modules/data/models/Data.py b/cognee/modules/data/models/Data.py index f1b033dd0..e24bc7c5c 100644 --- a/cognee/modules/data/models/Data.py +++ b/cognee/modules/data/models/Data.py @@ -1,7 +1,6 @@ from datetime import datetime, timezone from typing import List from uuid import uuid4 - from sqlalchemy import UUID, Column, DateTime, String from sqlalchemy.orm import Mapped, relationship @@ -19,6 +18,8 @@ class Data(Base): extension = Column(String) mime_type = Column(String) raw_data_location = Column(String) + owner_id = Column(UUID, index=True) + content_hash = Column(String) created_at = Column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc) ) diff --git a/cognee/modules/data/operations/write_metadata.py b/cognee/modules/data/operations/write_metadata.py index 43031cdc9..67c8c0e45 100644 --- a/cognee/modules/data/operations/write_metadata.py +++ b/cognee/modules/data/operations/write_metadata.py @@ -2,7 +2,6 @@ import json import re import warnings -from typing import Any from uuid import UUID from sqlalchemy import select from typing import Any, BinaryIO, Union diff --git a/cognee/modules/ingestion/data_types/BinaryData.py b/cognee/modules/ingestion/data_types/BinaryData.py index 6959eb15f..0606250ea 100644 --- a/cognee/modules/ingestion/data_types/BinaryData.py +++ b/cognee/modules/ingestion/data_types/BinaryData.py @@ -17,7 +17,7 @@ def __init__(self, data: BinaryIO, name: str = None): def get_identifier(self): metadata = self.get_metadata() - return self.name + "." + metadata["extension"] + return metadata["content_hash"] def get_metadata(self): self.ensure_metadata() diff --git a/cognee/modules/ingestion/identify.py b/cognee/modules/ingestion/identify.py index 745aab913..776932683 100644 --- a/cognee/modules/ingestion/identify.py +++ b/cognee/modules/ingestion/identify.py @@ -1,7 +1,9 @@ from uuid import uuid5, NAMESPACE_OID from .data_types import IngestionData -def identify(data: IngestionData) -> str: - data_id: str = data.get_identifier() +from cognee.modules.users.models import User - return uuid5(NAMESPACE_OID, data_id) +def identify(data: IngestionData, user: User) -> str: + data_content_hash: str = data.get_identifier() + # return UUID hash of file contents + owner id + return uuid5(NAMESPACE_OID,f"{data_content_hash}{user.id}") diff --git a/cognee/tasks/ingestion/ingest_data_with_metadata.py b/cognee/tasks/ingestion/ingest_data_with_metadata.py index 59e4a16f3..73e9da32d 100644 --- a/cognee/tasks/ingestion/ingest_data_with_metadata.py +++ b/cognee/tasks/ingestion/ingest_data_with_metadata.py @@ -5,7 +5,6 @@ from cognee.infrastructure.databases.relational import get_relational_engine from cognee.modules.data.methods import create_dataset from cognee.modules.data.models.DatasetData import DatasetData -from cognee.modules.data.operations.delete_metadata import delete_metadata from cognee.modules.users.models import User from cognee.modules.users.permissions.methods import give_permission_on_document from cognee.shared.utils import send_telemetry @@ -25,11 +24,11 @@ async def ingest_data_with_metadata(data: Any, dataset_name: str, user: User): ) @dlt.resource(standalone=True, merge_key="id") - async def data_resources(file_paths: str): + async def data_resources(file_paths: str, user: User): for file_path in file_paths: with open(file_path.replace("file://", ""), mode="rb") as file: classified_data = ingestion.classify(file) - data_id = ingestion.identify(classified_data) + data_id = ingestion.identify(classified_data, user) file_metadata = classified_data.get_metadata() yield { "id": data_id, @@ -37,6 +36,8 @@ async def data_resources(file_paths: str): "file_path": file_metadata["file_path"], "extension": file_metadata["extension"], "mime_type": file_metadata["mime_type"], + "content_hash": file_metadata["content_hash"], + "owner_id": str(user.id), } async def data_storing(data: Any, dataset_name: str, user: User): @@ -58,7 +59,8 @@ async def data_storing(data: Any, dataset_name: str, user: User): with open(file_path.replace("file://", ""), mode = "rb") as file: classified_data = ingestion.classify(file) - data_id = ingestion.identify(classified_data) + # data_id is the hash of file contents + owner id to avoid duplicate data + data_id = ingestion.identify(classified_data, user) file_metadata = classified_data.get_metadata() @@ -71,6 +73,7 @@ async def data_storing(data: Any, dataset_name: str, user: User): async with db_engine.get_async_session() as session: dataset = await create_dataset(dataset_name, user.id, session) + # Check to see if data should be updated data_point = ( await session.execute(select(Data).filter(Data.id == data_id)) ).scalar_one_or_none() @@ -80,6 +83,8 @@ async def data_storing(data: Any, dataset_name: str, user: User): data_point.raw_data_location = file_metadata["file_path"] data_point.extension = file_metadata["extension"] data_point.mime_type = file_metadata["mime_type"] + data_point.owner_id = user.id + data_point.content_hash = file_metadata["content_hash"] await session.merge(data_point) else: data_point = Data( @@ -87,7 +92,9 @@ async def data_storing(data: Any, dataset_name: str, user: User): name = file_metadata["name"], raw_data_location = file_metadata["file_path"], extension = file_metadata["extension"], - mime_type = file_metadata["mime_type"] + mime_type = file_metadata["mime_type"], + owner_id = user.id, + content_hash = file_metadata["content_hash"], ) # Check if data is already in dataset @@ -118,14 +125,14 @@ async def data_storing(data: Any, dataset_name: str, user: User): # To use sqlite with dlt dataset_name must be set to "main". # Sqlite doesn't support schemas run_info = pipeline.run( - data_resources(file_paths), + data_resources(file_paths, user), table_name="file_metadata", dataset_name="main", write_disposition="merge", ) else: run_info = pipeline.run( - data_resources(file_paths), + data_resources(file_paths, user), table_name="file_metadata", dataset_name=dataset_name, write_disposition="merge", From 378e7b81a53423048bc6ec29e2d7fe968ab757b6 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 5 Dec 2024 17:03:36 +0100 Subject: [PATCH 04/10] fix: Fix merge of data for dlt Resolve issue with dlt data not being merged for data_id Fix COG-505 --- cognee/tasks/ingestion/ingest_data_with_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cognee/tasks/ingestion/ingest_data_with_metadata.py b/cognee/tasks/ingestion/ingest_data_with_metadata.py index 73e9da32d..0fd58168a 100644 --- a/cognee/tasks/ingestion/ingest_data_with_metadata.py +++ b/cognee/tasks/ingestion/ingest_data_with_metadata.py @@ -23,7 +23,7 @@ async def ingest_data_with_metadata(data: Any, dataset_name: str, user: User): destination = destination, ) - @dlt.resource(standalone=True, merge_key="id") + @dlt.resource(standalone=True, primary_key="id", merge_key="id") async def data_resources(file_paths: str, user: User): for file_path in file_paths: with open(file_path.replace("file://", ""), mode="rb") as file: From 813b76c9c2e1ec89218d26c5e5272f4689261f04 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 5 Dec 2024 19:25:50 +0100 Subject: [PATCH 05/10] test: Add test for text deduplication Added end to end test for text deduplication Test COG-505 --- .github/workflows/test_deduplication.yml | 54 +++++++ .../ingestion/ingest_data_with_metadata.py | 3 +- .../Natural_language_processing_copy.txt | 2 + cognee/tests/test_data/example.png | Bin 0 -> 10784 bytes cognee/tests/test_data/example_copy.png | Bin 0 -> 10784 bytes cognee/tests/test_data/text_to_speech.mp3 | Bin 0 -> 28173 bytes .../tests/test_data/text_to_speech_copy.mp3 | Bin 0 -> 28173 bytes cognee/tests/test_deduplication.py | 153 ++++++++++++++++++ 8 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test_deduplication.yml create mode 100644 cognee/tests/test_data/Natural_language_processing_copy.txt create mode 100644 cognee/tests/test_data/example.png create mode 100644 cognee/tests/test_data/example_copy.png create mode 100644 cognee/tests/test_data/text_to_speech.mp3 create mode 100644 cognee/tests/test_data/text_to_speech_copy.mp3 create mode 100644 cognee/tests/test_deduplication.py diff --git a/.github/workflows/test_deduplication.yml b/.github/workflows/test_deduplication.yml new file mode 100644 index 000000000..46312060e --- /dev/null +++ b/.github/workflows/test_deduplication.yml @@ -0,0 +1,54 @@ +name: test | deduplication + +on: + workflow_dispatch: + pull_request: + branches: + - main + types: [labeled, synchronize] + + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + RUNTIME__LOG_LEVEL: ERROR + +jobs: + get_docs_changes: + name: docs changes + uses: ./.github/workflows/get_docs_changes.yml + + start_postgres: + name: test + needs: get_docs_changes + if: needs.get_docs_changes.outputs.changes_outside_docs == 'true' && ${{ github.event.label.name == 'run-checks' }} + runs-on: ubuntu-latest + defaults: + run: + shell: bash + services: + postgres: + image: pgvector/pgvector:pg17 + env: + POSTGRES_USER: cognee + POSTGRES_PASSWORD: cognee + POSTGRES_DB: cognee_db + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + + run_simple_example_test: + needs: start_postgres + uses: ./.github/workflows/reusable_python_example.yml + with: + example-location: ./cognee/tests/test_deduplication.py + secrets: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + GRAPHISTRY_USERNAME: ${{ secrets.GRAPHISTRY_USERNAME }} + GRAPHISTRY_PASSWORD: ${{ secrets.GRAPHISTRY_PASSWORD }} diff --git a/cognee/tasks/ingestion/ingest_data_with_metadata.py b/cognee/tasks/ingestion/ingest_data_with_metadata.py index 0fd58168a..7a70881e2 100644 --- a/cognee/tasks/ingestion/ingest_data_with_metadata.py +++ b/cognee/tasks/ingestion/ingest_data_with_metadata.py @@ -131,10 +131,11 @@ async def data_storing(data: Any, dataset_name: str, user: User): write_disposition="merge", ) else: + # Data should be stored in the same schema to allow deduplication run_info = pipeline.run( data_resources(file_paths, user), table_name="file_metadata", - dataset_name=dataset_name, + dataset_name="public", write_disposition="merge", ) diff --git a/cognee/tests/test_data/Natural_language_processing_copy.txt b/cognee/tests/test_data/Natural_language_processing_copy.txt new file mode 100644 index 000000000..a6fad3b47 --- /dev/null +++ b/cognee/tests/test_data/Natural_language_processing_copy.txt @@ -0,0 +1,2 @@ +Natural language processing (NLP) is an interdisciplinary subfield of computer science and information retrieval. It is primarily concerned with giving computers the ability to support and manipulate human language. It involves processing natural language datasets, such as text corpora or speech corpora, using either rule-based or probabilistic (i.e. statistical and, most recently, neural network-based) machine learning approaches. The goal is a computer capable of "understanding"[citation needed] the contents of documents, including the contextual nuances of the language within them. To this end, natural language processing often borrows ideas from theoretical linguistics. The technology can then accurately extract information and insights contained in the documents as well as categorize and organize the documents themselves. +Challenges in natural language processing frequently involve speech recognition, natural-language understanding, and natural-language generation. diff --git a/cognee/tests/test_data/example.png b/cognee/tests/test_data/example.png new file mode 100644 index 0000000000000000000000000000000000000000..4d406cafdeb7e6d778ff3fa37d99d42088673bf9 GIT binary patch literal 10784 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 Z|FRx Date: Thu, 5 Dec 2024 19:37:12 +0100 Subject: [PATCH 06/10] test: Add testing of dataset data table content Add testing of dataset data table content Test COG-505 --- .github/workflows/test_deduplication.yml | 35 +++++++++++++++++------- cognee/tests/test_deduplication.py | 5 ++++ 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test_deduplication.yml b/.github/workflows/test_deduplication.yml index 46312060e..924aab130 100644 --- a/.github/workflows/test_deduplication.yml +++ b/.github/workflows/test_deduplication.yml @@ -20,7 +20,7 @@ jobs: name: docs changes uses: ./.github/workflows/get_docs_changes.yml - start_postgres: + run_deduplication_test: name: test needs: get_docs_changes if: needs.get_docs_changes.outputs.changes_outside_docs == 'true' && ${{ github.event.label.name == 'run-checks' }} @@ -43,12 +43,27 @@ jobs: ports: - 5432:5432 - run_simple_example_test: - needs: start_postgres - uses: ./.github/workflows/reusable_python_example.yml - with: - example-location: ./cognee/tests/test_deduplication.py - secrets: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - GRAPHISTRY_USERNAME: ${{ secrets.GRAPHISTRY_USERNAME }} - GRAPHISTRY_PASSWORD: ${{ secrets.GRAPHISTRY_PASSWORD }} + steps: + - name: Check out + uses: actions/checkout@master + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11.x' + + - name: Install Poetry + uses: snok/install-poetry@v1.3.2 + with: + virtualenvs-create: true + virtualenvs-in-project: true + installer-parallel: true + + - name: Install dependencies + run: poetry install -E postgres --no-interaction + + - name: Run deduplication test + env: + ENV: 'dev' + LLM_API_KEY: ${{ secrets.OPENAI_API_KEY }} + run: poetry run python ./cognee/tests/test_deduplication.py diff --git a/cognee/tests/test_deduplication.py b/cognee/tests/test_deduplication.py index b7bdccb4d..79936cba5 100644 --- a/cognee/tests/test_deduplication.py +++ b/cognee/tests/test_deduplication.py @@ -36,6 +36,11 @@ async def test_deduplication(): assert result[0]["name"] == dataset_name, "Result name does not match expected value." assert result[1]["name"] == dataset_name2, "Result name does not match expected value." + result = await relational_engine.get_all_data_from_table("dataset_data") + assert len(result) == 2, "Unexpected number of dataset data relationships found." + assert result[0]["data_id"] == result[1]["data_id"], "Data item is not reused between datasets." + assert result[0]["dataset_id"] != result[1]["dataset_id"], "Dataset items are not different." + await cognee.prune.prune_data() await cognee.prune.prune_system(metadata=True) From 9ba5d49e696a66eac0f384c8fed4d09068a68a0c Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 5 Dec 2024 20:09:29 +0100 Subject: [PATCH 07/10] test: Fix test for multimedia deduplication Add missing function to get data from database to multimedia deduplication test Test COG-505 --- cognee/tests/test_deduplication.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cognee/tests/test_deduplication.py b/cognee/tests/test_deduplication.py index 79936cba5..467a52368 100644 --- a/cognee/tests/test_deduplication.py +++ b/cognee/tests/test_deduplication.py @@ -74,6 +74,7 @@ async def test_deduplication(): await cognee.add([explanation_file_path], dataset_name) await cognee.add([explanation_file_path2], dataset_name2) + result = await relational_engine.get_all_data_from_table("data") assert len(result) == 1, "More than one data entity was found." await cognee.prune.prune_data() @@ -90,6 +91,7 @@ async def test_deduplication(): await cognee.add([explanation_file_path], dataset_name) await cognee.add([explanation_file_path2], dataset_name2) + result = await relational_engine.get_all_data_from_table("data") assert len(result) == 1, "More than one data entity was found." await cognee.prune.prune_data() From e80377b7297ad6f17e3d28d4341ce57307774d22 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 5 Dec 2024 20:33:30 +0100 Subject: [PATCH 08/10] refactor: Move hash calculation of file to util Moved hash calculation of file to shared utils, added better typing Refactor COG-505 --- .../files/utils/get_file_metadata.py | 3 ++- cognee/shared/utils.py | 23 +++++++++++++++++++ .../ingestion/ingest_data_with_metadata.py | 4 ++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/cognee/infrastructure/files/utils/get_file_metadata.py b/cognee/infrastructure/files/utils/get_file_metadata.py index a9528e5c0..89c3d6d8e 100644 --- a/cognee/infrastructure/files/utils/get_file_metadata.py +++ b/cognee/infrastructure/files/utils/get_file_metadata.py @@ -1,6 +1,7 @@ from typing import BinaryIO, TypedDict import hashlib from .guess_file_type import guess_file_type +from cognee.shared.utils import get_file_content_hash class FileMetadata(TypedDict): @@ -13,7 +14,7 @@ class FileMetadata(TypedDict): def get_file_metadata(file: BinaryIO) -> FileMetadata: """Get metadata from a file""" file.seek(0) - content_hash = hashlib.md5(file.read()).hexdigest() + content_hash = get_file_content_hash(file) file.seek(0) file_type = guess_file_type(file) diff --git a/cognee/shared/utils.py b/cognee/shared/utils.py index 315e234f1..1dc52acd5 100644 --- a/cognee/shared/utils.py +++ b/cognee/shared/utils.py @@ -1,6 +1,9 @@ """ This module contains utility functions for the cognee. """ import os +from typing import BinaryIO, Union + import requests +import hashlib from datetime import datetime, timezone import graphistry import networkx as nx @@ -70,6 +73,26 @@ def num_tokens_from_string(string: str, encoding_name: str) -> int: num_tokens = len(encoding.encode(string)) return num_tokens +def get_file_content_hash(file_obj: Union[str, BinaryIO]) -> str: + h = hashlib.md5() + + if isinstance(file_obj, str): + with open(file_obj, 'rb') as file: + while True: + # Reading is buffered, so we can read smaller chunks. + chunk = file.read(h.block_size) + if not chunk: + break + h.update(chunk) + else: + while True: + # Reading is buffered, so we can read smaller chunks. + chunk = file_obj.read(h.block_size) + if not chunk: + break + h.update(chunk) + + return h.hexdigest() def trim_text_to_max_tokens(text: str, max_tokens: int, encoding_name: str) -> str: """ diff --git a/cognee/tasks/ingestion/ingest_data_with_metadata.py b/cognee/tasks/ingestion/ingest_data_with_metadata.py index 7a70881e2..c6b42f482 100644 --- a/cognee/tasks/ingestion/ingest_data_with_metadata.py +++ b/cognee/tasks/ingestion/ingest_data_with_metadata.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, List import dlt import cognee.modules.ingestion as ingestion @@ -24,7 +24,7 @@ async def ingest_data_with_metadata(data: Any, dataset_name: str, user: User): ) @dlt.resource(standalone=True, primary_key="id", merge_key="id") - async def data_resources(file_paths: str, user: User): + async def data_resources(file_paths: List[str], user: User): for file_path in file_paths: with open(file_path.replace("file://", ""), mode="rb") as file: classified_data = ingestion.classify(file) From 1e098ae70d4449b53328157f8d8a86059f4f73da Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Thu, 5 Dec 2024 20:54:55 +0100 Subject: [PATCH 09/10] refactor: Add error handling to hash util Added error handling to reading of file in hash util Refactor COG-505 --- cognee/shared/exceptions/__init__.py | 9 ++++++++ cognee/shared/exceptions/exceptions.py | 11 ++++++++++ cognee/shared/utils.py | 29 +++++++++++++++----------- 3 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 cognee/shared/exceptions/__init__.py create mode 100644 cognee/shared/exceptions/exceptions.py diff --git a/cognee/shared/exceptions/__init__.py b/cognee/shared/exceptions/__init__.py new file mode 100644 index 000000000..9b86cccab --- /dev/null +++ b/cognee/shared/exceptions/__init__.py @@ -0,0 +1,9 @@ +""" +Custom exceptions for the Cognee API. + +This module defines a set of exceptions for handling various shared utility errors +""" + +from .exceptions import ( + IngestionError, +) \ No newline at end of file diff --git a/cognee/shared/exceptions/exceptions.py b/cognee/shared/exceptions/exceptions.py new file mode 100644 index 000000000..101711398 --- /dev/null +++ b/cognee/shared/exceptions/exceptions.py @@ -0,0 +1,11 @@ +from cognee.exceptions import CogneeApiError +from fastapi import status + +class IngestionError(CogneeApiError): + def __init__( + self, + message: str = "Failed to load data.", + name: str = "IngestionError", + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + ): + super().__init__(message, name, status_code) \ No newline at end of file diff --git a/cognee/shared/utils.py b/cognee/shared/utils.py index 1dc52acd5..b75076e55 100644 --- a/cognee/shared/utils.py +++ b/cognee/shared/utils.py @@ -19,6 +19,8 @@ from uuid import uuid4 import pathlib +from cognee.shared.exceptions import IngestionError + # Analytics Proxy Url, currently hosted by Vercel proxy_url = "https://test.prometh.ai" @@ -76,23 +78,26 @@ def num_tokens_from_string(string: str, encoding_name: str) -> int: def get_file_content_hash(file_obj: Union[str, BinaryIO]) -> str: h = hashlib.md5() - if isinstance(file_obj, str): - with open(file_obj, 'rb') as file: + try: + if isinstance(file_obj, str): + with open(file_obj, 'rb') as file: + while True: + # Reading is buffered, so we can read smaller chunks. + chunk = file.read(h.block_size) + if not chunk: + break + h.update(chunk) + else: while True: # Reading is buffered, so we can read smaller chunks. - chunk = file.read(h.block_size) + chunk = file_obj.read(h.block_size) if not chunk: break h.update(chunk) - else: - while True: - # Reading is buffered, so we can read smaller chunks. - chunk = file_obj.read(h.block_size) - if not chunk: - break - h.update(chunk) - - return h.hexdigest() + + return h.hexdigest() + except IOError as e: + raise IngestionError(message=f"Failed to load data from {file}: {e}") def trim_text_to_max_tokens(text: str, max_tokens: int, encoding_name: str) -> str: """ From cc6fbe2a5fc750b206d763e3ce45f16c9a371b79 Mon Sep 17 00:00:00 2001 From: Igor Ilic Date: Fri, 6 Dec 2024 13:48:39 +0100 Subject: [PATCH 10/10] refactor: Add space to ingest function Add space and newline to ingest function Refactor COG-505 --- cognee/modules/ingestion/identify.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cognee/modules/ingestion/identify.py b/cognee/modules/ingestion/identify.py index 776932683..977ff3f0b 100644 --- a/cognee/modules/ingestion/identify.py +++ b/cognee/modules/ingestion/identify.py @@ -3,7 +3,9 @@ from cognee.modules.users.models import User + def identify(data: IngestionData, user: User) -> str: data_content_hash: str = data.get_identifier() + # return UUID hash of file contents + owner id - return uuid5(NAMESPACE_OID,f"{data_content_hash}{user.id}") + return uuid5(NAMESPACE_OID, f"{data_content_hash}{user.id}")