Repository: topoteretes/cognee
Pull request #730: feature: tighten run_tasks_base
Author: login: lxobr
Labels: name: run-checks
Created: 2025-04-14T14:10:15Z | Merged: 2025-04-16T07:19:03Z

<!-- .github/pull_request_template.md -->

## Description
<!-- Provide a clear description of the changes in this PR -->
- Extracted run_tasks_base function into a new file run_tasks_base.py.
- Extracted four executors that execute core logic based on the task type.
- Extracted a task handler/wrapper that safely executes the core logic with logging and telemetry.
- Fixed the inconsistency with the batches of size 1.

## DCO Affirmation
I affirm that all code in every commit of this pull request conforms to the terms of the Topoteretes Developer Certificate of Origin.



Diff:
diff --git a/cognee/api/v1/cognify/code_graph_pipeline.py b/cognee/api/v1/cognify/code_graph_pipeline.py
index b6215b81be..f64cf292cf 100644
--- a/cognee/api/v1/cognify/code_graph_pipeline.py
+++ b/cognee/api/v1/cognify/code_graph_pipeline.py
@@ -9,7 +9,7 @@
 from cognee.base_config import get_base_config
 from cognee.modules.cognify.config import get_cognify_config
 from cognee.modules.pipelines import run_tasks
-from cognee.modules.pipelines.tasks.Task import Task
+from cognee.modules.pipelines.tasks.task import Task
 from cognee.modules.users.methods import get_default_user
 from cognee.shared.data_models import KnowledgeGraph, MonitoringTool
 from cognee.shared.utils import render_graph
diff --git a/cognee/api/v1/cognify/cognify.py b/cognee/api/v1/cognify/cognify.py
index a5912fecdb..8b38e53044 100644
--- a/cognee/api/v1/cognify/cognify.py
+++ b/cognee/api/v1/cognify/cognify.py
@@ -13,7 +13,7 @@
 from cognee.modules.pipelines import run_tasks
 from cognee.modules.pipelines.models import PipelineRunStatus
 from cognee.modules.pipelines.operations.get_pipeline_status import get_pipeline_status
-from cognee.modules.pipelines.tasks.Task import Task
+from cognee.modules.pipelines.tasks.task import Task
 from cognee.modules.users.methods import get_default_user
 from cognee.modules.users.models import User
 from cognee.shared.data_models import KnowledgeGraph
diff --git a/cognee/eval_framework/corpus_builder/corpus_builder_executor.py b/cognee/eval_framework/corpus_builder/corpus_builder_executor.py
index a2285e386b..a5a7d41642 100644
--- a/cognee/eval_framework/corpus_builder/corpus_builder_executor.py
+++ b/cognee/eval_framework/corpus_builder/corpus_builder_executor.py
@@ -4,7 +4,7 @@
 
 from cognee.eval_framework.benchmark_adapters.benchmark_adapters import BenchmarkAdapter
 from cognee.modules.chunking.TextChunker import TextChunker
-from cognee.modules.pipelines.tasks.Task import Task
+from cognee.modules.pipelines.tasks.task import Task
 
 logger = get_logger(level=ERROR)
 
diff --git a/cognee/eval_framework/corpus_builder/task_getters/TaskGetters.py b/cognee/eval_framework/corpus_builder/task_getters/TaskGetters.py
index a25a3c1c1f..bb5c1acf46 100644
--- a/cognee/eval_framework/corpus_builder/task_getters/TaskGetters.py
+++ b/cognee/eval_framework/corpus_builder/task_getters/TaskGetters.py
@@ -1,7 +1,7 @@
 from enum import Enum
 from typing import Callable, Awaitable, List
 from cognee.api.v1.cognify.cognify import get_default_tasks
-from cognee.modules.pipelines.tasks.Task import Task
+from cognee.modules.pipelines.tasks.task import Task
 from cognee.eval_framework.corpus_builder.task_getters.get_cascade_graph_tasks import (
     get_cascade_graph_tasks,
 )
diff --git a/cognee/eval_framework/corpus_builder/task_getters/get_cascade_graph_tasks.py b/cognee/eval_framework/corpus_builder/task_getters/get_cascade_graph_tasks.py
index ca77facef6..c35f51afa2 100644
--- a/cognee/eval_framework/corpus_builder/task_getters/get_cascade_graph_tasks.py
+++ b/cognee/eval_framework/corpus_builder/task_getters/get_cascade_graph_tasks.py
@@ -2,7 +2,7 @@
 from pydantic import BaseModel
 
 from cognee.modules.cognify.config import get_cognify_config
-from cognee.modules.pipelines.tasks.Task import Task
+from cognee.modules.pipelines.tasks.task import Task
 from cognee.modules.users.methods import get_default_user
 from cognee.modules.users.models import User
 from cognee.shared.data_models import KnowledgeGraph
diff --git a/cognee/eval_framework/corpus_builder/task_getters/get_default_tasks_by_indices.py b/cognee/eval_framework/corpus_builder/task_getters/get_default_tasks_by_indices.py
index e589f3f672..be532232fe 100644
--- a/cognee/eval_framework/corpus_builder/task_getters/get_default_tasks_by_indices.py
+++ b/cognee/eval_framework/corpus_builder/task_getters/get_default_tasks_by_indices.py
@@ -1,6 +1,6 @@
 from typing import List
 from cognee.api.v1.cognify.cognify import get_default_tasks
-from cognee.modules.pipelines.tasks.Task import Task
+from cognee.modules.pipelines.tasks.task import Task
 from cognee.modules.chunking.TextChunker import TextChunker
 from cognee.tasks.graph import extract_graph_from_data
 from cognee.tasks.storage import add_data_points
@@ -39,9 +39,10 @@ async def get_no_summary_tasks(
         extract_graph_from_data,
         graph_model=graph_model,
         ontology_adapter=ontology_adapter,
+        task_config={"batch_size": 10},
     )
 
-    add_data_points_task = Task(add_data_points)
+    add_data_points_task = Task(add_data_points, task_config={"batch_size": 10})
 
     return base_tasks + [graph_task, add_data_points_task]
 
@@ -53,6 +54,6 @@ async def get_just_chunks_tasks(
     # Get base tasks (0=classify, 1=check_permissions, 2=extract_chunks)
     base_tasks = await get_default_tasks_by_indices([0, 1, 2], chunk_size, chunker)
 
-    add_data_points_task = Task(add_data_points)
+    add_data_points_task = Task(add_data_points, task_config={"batch_size": 10})
 
     return base_tasks + [add_data_points_task]
diff --git a/cognee/modules/pipelines/__init__.py b/cognee/modules/pipelines/__init__.py
index 5005c25f03..52a0942a60 100644
--- a/cognee/modules/pipelines/__init__.py
+++ b/cognee/modules/pipelines/__init__.py
@@ -1,3 +1,3 @@
-from .tasks.Task import Task
+from .tasks.task import Task
 from .operations.run_tasks import run_tasks
 from .operations.run_parallel import run_tasks_parallel
diff --git a/cognee/modules/pipelines/operations/run_parallel.py b/cognee/modules/pipelines/operations/run_parallel.py
index d1774299b8..63ac2f7634 100644
--- a/cognee/modules/pipelines/operations/run_parallel.py
+++ b/cognee/modules/pipelines/operations/run_parallel.py
@@ -1,6 +1,6 @@
 from typing import Any, Callable, Generator, List
 import asyncio
-from ..tasks.Task import Task
+from ..tasks.task import Task
 
 
 def run_tasks_parallel(tasks: List[Task]) -> Callable[[Any], Generator[Any, Any, Any]]:
diff --git a/cognee/modules/pipelines/operations/run_tasks.py b/cognee/modules/pipelines/operations/run_tasks.py
index 242f74320b..e872597531 100644
--- a/cognee/modules/pipelines/operations/run_tasks.py
+++ b/cognee/modules/pipelines/operations/run_tasks.py
@@ -1,4 +1,3 @@
-import inspect
 import json
 from cognee.shared.logging_utils import get_logger
 from uuid import UUID, uuid4
@@ -15,212 +14,12 @@
 from cognee.shared.utils import send_telemetry
 from uuid import uuid5, NAMESPACE_OID
 
-from ..tasks.Task import Task
+from .run_tasks_base import run_tasks_base
+from ..tasks.task import Task
 
 logger = get_logger("run_tasks(tasks: [Task], data)")
 
 
-async def run_tasks_base(tasks: list[Task], data=None, user: User = None):
-    if len(tasks) == 0:
-        yield data
-        return
-
-    args = [data] if data is not None else []
-
-    running_task = tasks[0]
-    leftover_tasks = tasks[1:]
-    next_task = leftover_tasks[0] if len(leftover_tasks) > 0 else None
-    next_task_batch_size = next_task.task_config["batch_size"] if next_task else 1
-
-    if inspect.isasyncgenfunction(running_task.executable):
-        logger.info("Async generator task started: `%s`", running_task.executable.__name__)
-        send_telemetry(
-            "Async Generator Task Started",
-            user.id,
-            {
-                "task_name": running_task.executable.__name__,
-            },
-        )
-        try:
-            results = []
-
-            async_iterator = running_task.run(*args)
-
-            async for partial_result in async_iterator:
-                results.append(partial_result)
-
-                if len(results) == next_task_batch_size:
-                    async for result in run_tasks_base(
-                        leftover_tasks,
-                        results[0] if next_task_batch_size == 1 else results,
-                        user=user,
-                    ):
-                        yield result
-
-                    results = []
-
-            if len(results) > 0:
-                async for result in run_tasks_base(leftover_tasks, results, user):
-                    yield result
-
-                results = []
-
-            logger.info("Async generator task completed: `%s`", running_task.executable.__name__)
-            send_telemetry(
-                "Async Generator Task Completed",
-                user.id,
-                {
-                    "task_name": running_task.executable.__name__,
-                },
-            )
-        except Exception as error:
-            logger.error(
-                "Async generator task errored: `%s`\n%s\n",
-                running_task.executable.__name__,
-                str(error),
-                exc_info=True,
-            )
-            send_telemetry(
-                "Async Generator Task Errored",
-                user.id,
-                {
-                    "task_name": running_task.executable.__name__,
-                },
-            )
-            raise error
-
-    elif inspect.isgeneratorfunction(running_task.executable):
-        logger.info("Generator task started: `%s`", running_task.executable.__name__)
-        send_telemetry(
-            "Generator Task Started",
-            user.id,
-            {
-                "task_name": running_task.executable.__name__,
-            },
-        )
-        try:
-            results = []
-
-            for partial_result in running_task.run(*args):
-                results.append(partial_result)
-
-                if len(results) == next_task_batch_size:
-                    async for result in run_tasks_base(
-                        leftover_tasks, results[0] if next_task_batch_size == 1 else results, user
-                    ):
-                        yield result
-
-                    results = []
-
-            if len(results) > 0:
-                async for result in run_tasks_base(leftover_tasks, results, user):
-                    yield result
-
-                results = []
-
-            logger.info("Generator task completed: `%s`", running_task.executable.__name__)
-            send_telemetry(
-                "Generator Task Completed",
-                user_id=user.id,
-                additional_properties={
-                    "task_name": running_task.executable.__name__,
-                },
-            )
-        except Exception as error:
-            logger.error(
-                "Generator task errored: `%s`\n%s\n",
-                running_task.executable.__name__,
-                str(error),
-                exc_info=True,
-            )
-            send_telemetry(
-                "Generator Task Errored",
-                user_id=user.id,
-                additional_properties={
-                    "task_name": running_task.executable.__name__,
-                },
-            )
-            raise error
-
-    elif inspect.iscoroutinefunction(running_task.executable):
-        logger.info("Coroutine task started: `%s`", running_task.executable.__name__)
-        send_telemetry(
-            "Coroutine Task Started",
-            user_id=user.id,
-            additional_properties={
-                "task_name": running_task.executable.__name__,
-            },
-        )
-        try:
-            task_result = await running_task.run(*args)
-
-            async for result in run_tasks_base(leftover_tasks, task_result, user):
-                yield result
-
-            logger.info("Coroutine task completed: `%s`", running_task.executable.__name__)
-            send_telemetry(
-                "Coroutine Task Completed",
-                user.id,
-                {
-                    "task_name": running_task.executable.__name__,
-                },
-            )
-        except Exception as error:
-            logger.error(
-                "Coroutine task errored: `%s`\n%s\n",
-                running_task.executable.__name__,
-                str(error),
-                exc_info=True,
-            )
-            send_telemetry(
-                "Coroutine Task Errored",
-                user.id,
-                {
-                    "task_name": running_task.executable.__name__,
-                },
-            )
-            raise error
-
-    elif inspect.isfunction(running_task.executable):
-        logger.info("Function task started: `%s`", running_task.executable.__name__)
-        send_telemetry(
-            "Function Task Started",
-            user.id,
-            {
-                "task_name": running_task.executable.__name__,
-            },
-        )
-        try:
-            task_result = running_task.run(*args)
-
-            async for result in run_tasks_base(leftover_tasks, task_result, user):
-                yield result
-
-            logger.info("Function task completed: `%s`", running_task.executable.__name__)
-            send_telemetry(
-                "Function Task Completed",
-                user.id,
-                {
-                    "task_name": running_task.executable.__name__,
-                },
-            )
-        except Exception as error:
-            logger.error(
-                "Function task errored: `%s`\n%s\n",
-                running_task.executable.__name__,
-                str(error),
-                exc_info=True,
-            )
-            send_telemetry(
-                "Function Task Errored",
-                user.id,
-                {
-                    "task_name": running_task.executable.__name__,
-                },
-            )
-            raise error
-
-
 async def run_tasks_with_telemetry(tasks: list[Task], data, pipeline_name: str):
     config = get_current_settings()
 
diff --git a/cognee/modules/pipelines/operations/run_tasks_base.py b/cognee/modules/pipelines/operations/run_tasks_base.py
new file mode 100644
index 0000000000..9c5cdfdb81
--- /dev/null
+++ b/cognee/modules/pipelines/operations/run_tasks_base.py
@@ -0,0 +1,72 @@
+import inspect
+from cognee.shared.logging_utils import get_logger
+from cognee.modules.users.models import User
+from cognee.shared.utils import send_telemetry
+
+from ..tasks.task import Task
+
+logger = get_logger("run_tasks_base")
+
+
+async def handle_task(
+    running_task: Task,
+    args: list,
+    leftover_tasks: list[Task],
+    next_task_batch_size: int,
+    user: User,
+):
+    """Handle common task workflow with logging, telemetry, and error handling around the core execution logic."""
+    task_type = running_task.task_type
+
+    logger.info(f"{task_type} task started: `{running_task.executable.__name__}`")
+    send_telemetry(
+        f"{task_type} Task Started",
+        user_id=user.id,
+        additional_properties={
+            "task_name": running_task.executable.__name__,
+        },
+    )
+
+    try:
+        async for result_data in running_task.execute(args, next_task_batch_size):
+            async for result in run_tasks_base(leftover_tasks, result_data, user):
+                yield result
+
+        logger.info(f"{task_type} task completed: `{running_task.executable.__name__}`")
+        send_telemetry(
+            f"{task_type} Task Completed",
+            user_id=user.id,
+            additional_properties={
+                "task_name": running_task.executable.__name__,
+            },
+        )
+    except Exception as error:
+        logger.error(
+            f"{task_type} task errored: `{running_task.executable.__name__}`\n{str(error)}\n",
+            exc_info=True,
+        )
+        send_telemetry(
+            f"{task_type} Task Errored",
+            user_id=user.id,
+            additional_properties={
+                "task_name": running_task.executable.__name__,
+            },
+        )
+        raise error
+
+
+async def run_tasks_base(tasks: list[Task], data=None, user: User = None):
+    """Base function to execute tasks in a pipeline, handling task type detection and execution."""
+    if len(tasks) == 0:
+        yield data
+        return
+
+    args = [data] if data is not None else []
+
+    running_task = tasks[0]
+    leftover_tasks = tasks[1:]
+    next_task = leftover_tasks[0] if len(leftover_tasks) > 0 else None
+    next_task_batch_size = next_task.task_config["batch_size"] if next_task else 1
+
+    async for result in handle_task(running_task, args, leftover_tasks, next_task_batch_size, user):
+        yield result
diff --git a/cognee/modules/pipelines/tasks/Task.py b/cognee/modules/pipelines/tasks/Task.py
deleted file mode 100644
index 753152d0dd..0000000000
--- a/cognee/modules/pipelines/tasks/Task.py
+++ /dev/null
@@ -1,30 +0,0 @@
-from typing import Union, Callable, Any, Coroutine, Generator, AsyncGenerator
-
-
-class Task:
-    executable: Union[
-        Callable[..., Any],
-        Callable[..., Coroutine[Any, Any, Any]],
-        Generator[Any, Any, Any],
-        AsyncGenerator[Any, Any],
-    ]
-    task_config: dict[str, Any] = {
-        "batch_size": 1,
-    }
-    default_params: dict[str, Any] = {}
-
-    def __init__(self, executable, *args, task_config=None, **kwargs):
-        self.executable = executable
-        self.default_params = {"args": args, "kwargs": kwargs}
-
-        if task_config is not None:
-            self.task_config = task_config
-
-            if "batch_size" not in task_config:
-                self.task_config["batch_size"] = 1
-
-    def run(self, *args, **kwargs):
-        combined_args = args + self.default_params["args"]
-        combined_kwargs = {**self.default_params["kwargs"], **kwargs}
-
-        return self.executable(*combined_args, **combined_kwargs)
diff --git a/cognee/modules/pipelines/tasks/task.py b/cognee/modules/pipelines/tasks/task.py
new file mode 100644
index 0000000000..7ace6b3582
--- /dev/null
+++ b/cognee/modules/pipelines/tasks/task.py
@@ -0,0 +1,97 @@
+from typing import Union, Callable, Any, Coroutine, Generator, AsyncGenerator
+import inspect
+
+
+class Task:
+    executable: Union[
+        Callable[..., Any],
+        Callable[..., Coroutine[Any, Any, Any]],
+        Generator[Any, Any, Any],
+        AsyncGenerator[Any, Any],
+    ]
+    task_config: dict[str, Any] = {
+        "batch_size": 1,
+    }
+    default_params: dict[str, Any] = {}
+    task_type: str = None
+    _execute_method: Callable = None
+    _next_batch_size: int = 1
+
+    def __init__(self, executable, *args, task_config=None, **kwargs):
+        self.executable = executable
+        self.default_params = {"args": args, "kwargs": kwargs}
+
+        if inspect.isasyncgenfunction(executable):
+            self.task_type = "Async Generator"
+            self._execute_method = self.execute_async_generator
+        elif inspect.isgeneratorfunction(executable):
+            self.task_type = "Generator"
+            self._execute_method = self.execute_generator
+        elif inspect.iscoroutinefunction(executable):
+            self.task_type = "Coroutine"
+            self._execute_method = self.execute_coroutine
+        elif inspect.isfunction(executable):
+            self.task_type = "Function"
+            self._execute_method = self.execute_function
+        else:
+            raise ValueError(f"Unsupported task type: {executable}")
+
+        if task_config is not None:
+            self.task_config = task_config
+
+            if "batch_size" not in task_config:
+                self.task_config["batch_size"] = 1
+
+    def run(self, *args, **kwargs):
+        """Execute the underlying task with given arguments."""
+        combined_args = args + self.default_params["args"]
+        combined_kwargs = {**self.default_params["kwargs"], **kwargs}
+
+        return self.executable(*combined_args, **combined_kwargs)
+
+    async def execute_async_generator(self, args):
+        """Execute async generator task and collect results in batches."""
+        results = []
+        async_iterator = self.run(*args)
+
+        async for partial_result in async_iterator:
+            results.append(partial_result)
+
+            if len(results) == self._next_batch_size:
+                yield results
+                results = []
+
+        if results:
+            yield results
+
+    async def execute_generator(self, args):
+        """Execute generator task and collect results in batches."""
+        results = []
+
+        for partial_result in self.run(*args):
+            results.append(partial_result)
+
+            if len(results) == self._next_batch_size:
+                yield results
+                results = []
+
+        if results:
+            yield results
+
+    async def execute_coroutine(self, args):
+        """Execute coroutine task and yield the result."""
+        task_result = await self.run(*args)
+        yield task_result
+
+    async def execute_function(self, args):
+        """Execute function task and yield the result."""
+        task_result = self.run(*args)
+        yield task_result
+
+    async def execute(self, args, next_batch_size=None):
+        """Execute the task based on its type and yield results with the next task's batch size."""
+        if next_batch_size is not None:
+            self._next_batch_size = next_batch_size
+
+        async for result in self._execute_method(args):
+            yield result
diff --git a/cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py b/cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py
index 0aa60f3b26..4aabb4241f 100644
--- a/cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py
+++ b/cognee/tests/integration/run_toy_tasks/run_task_from_queue_test.py
@@ -3,7 +3,7 @@
 
 import cognee
 from cognee.modules.pipelines.operations.run_tasks import run_tasks_base
-from cognee.modules.pipelines.tasks.Task import Task
+from cognee.modules.pipelines.tasks.task import Task
 from cognee.modules.users.methods import get_default_user
 from cognee.infrastructure.databases.relational import create_db_and_tables
 
@@ -19,11 +19,11 @@ async def queue_consumer():
             else:
                 await asyncio.sleep(0.3)
 
-    async def add_one(num):
-        yield num + 1
+    async def add_one(num_list):
+        yield num_list[0] + 1
 
-    async def multiply_by_two(num):
-        yield num * 2
+    async def multiply_by_two(num_list):
+        yield num_list[0] * 2
 
     await create_db_and_tables()
     user = await get_default_user()
@@ -41,7 +41,7 @@ async def multiply_by_two(num):
     results = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
     index = 0
     async for result in tasks_run:
-        assert result == results[index], f"at {index = }: {result = } != {results[index] = }"
+        assert result[0] == results[index], f"at {index = }: {result = } != {results[index] = }"
         index += 1
 
 
diff --git a/cognee/tests/integration/run_toy_tasks/run_tasks_test.py b/cognee/tests/integration/run_toy_tasks/run_tasks_test.py
index b1400beb59..7fbc905ee3 100644
--- a/cognee/tests/integration/run_toy_tasks/run_tasks_test.py
+++ b/cognee/tests/integration/run_toy_tasks/run_tasks_test.py
@@ -2,7 +2,7 @@
 
 import cognee
 from cognee.modules.pipelines.operations.run_tasks import run_tasks_base
-from cognee.modules.pipelines.tasks.Task import Task
+from cognee.modules.pipelines.tasks.task import Task
 from cognee.modules.users.methods import get_default_user
 from cognee.infrastructure.databases.relational import create_db_and_tables
 
@@ -19,11 +19,11 @@ async def add_one(nums):
         for num in nums:
             yield num + 1
 
-    async def multiply_by_two(num):
-        yield num * 2
+    async def multiply_by_two(nums):
+        yield nums[0] * 2
 
-    async def add_one_single(num):
-        yield num + 1
+    async def add_one_single(nums):
+        yield nums[0] + 1
 
     await create_db_and_tables()
     user = await get_default_user()
@@ -42,7 +42,7 @@ async def add_one_single(num):
     results = [5, 7, 9, 11, 13, 15, 17, 19, 21, 23]
     index = 0
     async for result in pipeline:
-        assert result == results[index], f"at {index = }: {result = } != {results[index] = }"
+        assert result[0] == results[index], f"at {index = }: {result = } != {results[index] = }"
         index += 1
 
 
diff --git a/examples/python/pokemon_datapoints_example.py b/examples/python/pokemon_datapoints_example.py
index 1c51ae468c..e67967529a 100644
--- a/examples/python/pokemon_datapoints_example.py
+++ b/examples/python/pokemon_datapoints_example.py
@@ -13,7 +13,7 @@
 from cognee.low_level import DataPoint, setup as cognee_setup
 from cognee.api.v1.search import SearchType
 from cognee.tasks.storage import add_data_points
-from cognee.modules.pipelines.tasks.Task import Task
+from cognee.modules.pipelines.tasks.task import Task
 from cognee.modules.pipelines import run_tasks
 
 


Reviews:
user: login: coderabbitai[bot]
body: > [!CAUTION]
> Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments.  If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

**Actionable comments posted: 9**

<details>
<summary>🧹 Nitpick comments (38)</summary><blockquote>

<details>
<summary>cognee/infrastructure/llm/prompts/answer_simple_question_benchmark4.txt (1)</summary><blockquote>

`1-15`: **Review of Prompt Instructions Document**

The new file clearly documents the standardized protocol for generating concise, context-based answers. The prompt instructions are well-structured, detailing:
- **Minimalism:** (Line 3) advising brevity.
- **Question-Specific Responses:** (Lines 4–8) with explicit instructions for handling yes/no, what/who/where, when, and how/why questions.
- **Formatting Rules:** (Lines 9–11) enforcing no punctuation and that responses be in lowercase.
- **Context-Only Responses:** (Line 12) ensuring that answers are derived solely from the provided context.
- **Overall Protocol Meaning:** (Line 14) summarizing the design goal for direct communication.

**Suggestions:**
- Consider adding a short header or file description at the very top (before line 1) to clarify the file’s purpose for future maintainers.
- Optionally remove or repurpose extra blank lines (such as after line 2) to enhance compactness and consistency with other benchmark prompt files.
  
Overall, the document is consistent and clear in its intent.

</blockquote></details>
<details>
<summary>cognee/infrastructure/llm/prompts/answer_simple_question_benchmark2.txt (1)</summary><blockquote>

`2-7`: **Bullet Instruction Clarity and Style Suggestion**

The bullet points effectively specify the response guidelines for various question types. However, the repeated use of a similar structure (each bullet beginning with "- For") may trigger style warnings and could benefit from slight rewordings to enhance readability. Consider varying the sentence starters in some bullets to avoid stylistic repetition.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[style] ~5-~5: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...y with a single word or brief phrase. - For when questions: return only the relevan...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

---

[style] ~6-~6: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...: return only the relevant date/time. - For how/why questions: use the briefest phr...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

</details>

</details>

</blockquote></details>
<details>
<summary>cognee/infrastructure/llm/prompts/generate_graph_prompt_guided.txt (2)</summary><blockquote>

`10-15`: **Node Label Consistency – Wording Improvement**  
The guideline on label consistency is clear; however, the phrase on line 14 ("in a case of multiple words separated by whitespaces") could be rephrased for clarity. For example: "Each entity type should be singular, and if it consists of multiple words, they should be separated by a single space."

---

`72-74`: **Inferred Facts Guidelines – Clarity Enhancement**  
The guideline on extracting inferred facts is valuable for enhancing the knowledge graph. To avoid ambiguity, consider adding a brief example or additional context on what qualifies as an inferred fact.

</blockquote></details>
<details>
<summary>cognee/infrastructure/llm/prompts/generate_graph_prompt_strict.txt (2)</summary><blockquote>

`31-49`: **Clear Guidance on Dates, Numbers, and Properties**

The sections on date formatting and numerical properties are detailed and unambiguous. The emphasis on using the "YYYY-MM-DD" format for dates and enforcing snake_case for properties adds necessary rigor.  
*Suggestion:* Including an explicit example of a key-value extraction for a numerical value might further aid implementers.

---

`89-89`: **Annotation Consistency**

Line 89 is missing the tilde (`~`) annotation that is consistently used on the other added lines. Please update this line's annotation to maintain consistency across the file.

</blockquote></details>
<details>
<summary>cognee/tasks/graph/extract_graph_from_data.py (1)</summary><blockquote>

`2-2`: **Unused import detected.**

The `Optional` type is imported but not used anywhere in this file. This creates unnecessary clutter.

```diff
-from typing import Type, List, Optional
+from typing import Type, List
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

2-2: `typing.Optional` imported but unused

Remove unused import: `typing.Optional`

(F401)

</details>

</details>

</blockquote></details>
<details>
<summary>cognee/infrastructure/llm/prompts/generate_graph_prompt_oneshot.txt (3)</summary><blockquote>

`30-32`: **Minor grammatical improvement opportunity.**

Consider revising the wording for clarity:

```diff
-  - Always use full, canonical names.
+  - Always use complete, canonical names.
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[grammar] ~30-~30: Did you mean the adjective “useful”?
Context: ...ived directly from the text.   - Always use full, canonical names.   - **Do not** use in...

(THANK_FULL)

</details>

</details>

---

`83-89`: **Date format consistency.**

The date example includes a missing comma after the year which is inconsistent with standard format:

```diff
-> **Input**: "Google was founded on September 4, 1998 and has a market cap of 800000000000."
+> **Input**: "Google was founded on September 4, 1998, and has a market cap of 800000000000."
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[style] ~83-~83: Some style guides suggest that commas should set off the year in a month-day-year date.
Context: ...**: "Google was founded on September 4, 1998 and has a market cap of 800000000000." ...

(MISSING_COMMA_AFTER_YEAR)

</details>

</details>

---

`127-132`: **Wording simplification opportunity.**

The phrase "absolutely essential" could be simplified for clarity:

```diff
-- **Rule**: Avoid vague or empty edges (e.g., "X is a concept") unless absolutely essential.
+- **Rule**: Avoid vague or empty edges (e.g., "X is a concept") unless essential.
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[style] ~127-~127: ‘absolutely essential’ might be wordy. Consider a shorter alternative.
Context: ...y edges (e.g., "X is a concept") unless absolutely essential.  ### 4.3 Inferred Facts - **Rule**: On...

(EN_WORDINESS_PREMIUM_ABSOLUTELY_ESSENTIAL)

</details>

</details>

</blockquote></details>
<details>
<summary>cognee/tasks/ingestion/migrate_relational_database.py (1)</summary><blockquote>

`148-168`: **Good implementation of duplicate edge removal.**

The `_remove_duplicate_edges` function efficiently eliminates duplicate edges using a set to track unique edges. The implementation correctly handles the dictionary attributes by converting them to a hashable representation.



Consider adding logging to track how many duplicates were removed:

```diff
def _remove_duplicate_edges(edge_mapping):
    seen = set()
    unique_original_shape = []

    for tup in edge_mapping:
        # We go through all the tuples in the edge_mapping and we only add unique tuples to the list
        # To eliminate duplicate edges.
        source_id, target_id, rel_name, rel_dict = tup
        # We need to convert the dictionary to a frozenset to be able to compare values for it
        rel_dict_hashable = frozenset(sorted(rel_dict.items()))
        hashable_tup = (source_id, target_id, rel_name, rel_dict_hashable)

        # We use the seen set to keep track of unique edges
        if hashable_tup not in seen:
            # A list that has frozensets elements instead of dictionaries is needed to be able to compare values
            seen.add(hashable_tup)
            # append the original tuple shape (with the dictionary) if it's the first time we see it
            unique_original_shape.append(tup)

+    duplicates_removed = len(edge_mapping) - len(unique_original_shape)
+    if duplicates_removed > 0:
+        logger.info(f"Removed {duplicates_removed} duplicate edges")
    return unique_original_shape
```

</blockquote></details>
<details>
<summary>cognee/base_config.py (1)</summary><blockquote>

`17-18`: **Ensure secure handling of default user credentials.**

Storing user credentials in environment variables is generally acceptable, but make sure they aren’t logged or exposed inadvertently. For enhanced security, consider using a secrets manager or vault-based approach.

</blockquote></details>
<details>
<summary>cognee/modules/data/extraction/knowledge_graph/extract_content_graph.py (1)</summary><blockquote>

`1-1`: **Remove unused import.**

The `Optional` import from `typing` is not being used in this file.

```diff
-from typing import Type, Optional
+from typing import Type
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

1-1: `typing.Optional` imported but unused

Remove unused import: `typing.Optional`

(F401)

</details>

</details>

</blockquote></details>
<details>
<summary>cognee/api/v1/config/config.py (1)</summary><blockquote>

`134-147`: **Fix documentation in docstring for migration config method.**

The docstring incorrectly refers to "relational db config" instead of "migration db config".

```diff
-        Updates the relational db config with values from config_dict.
+        Updates the migration db config with values from config_dict.
```

Otherwise, the implementation correctly follows the pattern used in other configuration methods, which provides consistency in the codebase.

</blockquote></details>
<details>
<summary>cognee/eval_framework/corpus_builder/run_corpus_builder.py (1)</summary><blockquote>

`3-3`: **Remove unused import**

The `Optional` type is imported but not used in the code.

```diff
-from typing import List, Optional
+from typing import List
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

3-3: `typing.Optional` imported but unused

Remove unused import: `typing.Optional`

(F401)

</details>

</details>

</blockquote></details>
<details>
<summary>cognee/infrastructure/databases/relational/config.py (2)</summary><blockquote>

`38-38`: **Consider using `Optional[str]` for readability.**

Instead of `Union[str, None]`, you may prefer `Optional[str]` from `typing` to make it more apparent that `None` is allowed and to align with typical Python conventions.

```diff
-    migration_db_name: Union[str, None] = None
+    from typing import Optional
+    migration_db_name: Optional[str] = None
```

---

`43-43`: **Apply the same convention for `migration_db_provider`.**

Similar to the suggestion above, opt for `Optional[str]`.

```diff
-    migration_db_provider: Union[str, None] = None
+    migration_db_provider: Optional[str] = None
```

</blockquote></details>
<details>
<summary>cognee/tests/test_cognee_server_start.py (2)</summary><blockquote>

`10-28`: **Enhance server startup reliability.**

Relying on a fixed 20-second `time.sleep` may cause flakiness on slower machines or waste time on faster ones. Consider a loop to check readiness (e.g., repeatedly try connecting until the server responds or a timeout is reached).

---

`38-44`: **Graceful teardown approach is acceptable.**

Using `os.killpg` with `SIGTERM` is typically safe. Just ensure the parent process group logic works properly on non-POSIX systems. In smaller test environments, a simple `terminate()` might suffice, but this approach is fine if you tested it cross-platform.

</blockquote></details>
<details>
<summary>.github/README_WORKFLOW_MIGRATION.md (1)</summary><blockquote>

`30-92`: **Good manual migration guide.**

The step-by-step approach to switch `on:` triggers to `workflow_call:` is straightforward. Consider adding a note on how to reference the new `test-suites.yml` file, if it’s in a non-obvious location.

</blockquote></details>
<details>
<summary>cognee/infrastructure/databases/vector/pgvector/PGVectorAdapter.py (1)</summary><blockquote>

`128-151`: **Potential performance concern in per-data-point lookups.**

The loop executes a `SELECT` for each data point to determine if it needs an update or insert. This may be inefficient for large batches. Consider a bulk approach (e.g., a single `SELECT` for all IDs, building a map, then updating or inserting accordingly) to reduce database roundtrips.

</blockquote></details>
<details>
<summary>evals/README.md (3)</summary><blockquote>

`66-69`: **Remove unnecessary blank lines.**

There are several empty lines before the "Human Evaluation" section that disrupt the document flow. Consider removing them to maintain consistent spacing throughout the document.

```diff
- 
- 
- 
#### Human Evaluation
```

---

`85-85`: **Fix grammar in the limitations list.**

The sentence has a grammar issue that affects readability.

```diff
- LLM as a judge metrics are not reliable measure and can indicate the overall accuracy
+ LLM as a judge metrics are not a reliable measure and can only approximate the overall accuracy
```

---

`87-87`: **Hyphenate the compound adjective "labor-intensive".**

According to English style conventions, compound adjectives should be hyphenated when they precede a noun.

```diff
- Human as a judge is labor intensive and does not scale
+ Human as a judge is labor-intensive and does not scale
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 LanguageTool</summary>

[misspelling] ~87-~87: This word is normally spelled with a hyphen.
Context: ...memory evaluation - Human as a judge is labor intensive and does not scale - Hotpot is not the ...

(EN_COMPOUNDS_LABOR_INTENSIVE)

</details>

</details>

</blockquote></details>
<details>
<summary>cognee/tests/test_relational_db_migration.py (2)</summary><blockquote>

`111-111`: **Rename unused loop variable.**

The loop control variable `edge_data` is not used within the loop body, which is flagged by static analysis.

```diff
- for src, tgt, key, edge_data in edges:
+ for src, tgt, key, _edge_data in edges:
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

111-111: Loop control variable `edge_data` not used within loop body

Rename unused `edge_data` to `_edge_data`

(B007)

</details>

</details>

---

`165-169`: **Replace magic numbers with named constants.**

The assertions use hardcoded numbers for expected node and edge counts, which makes the tests brittle and harder to maintain.

```diff
+ # Define expected counts at the top of the file or in a configuration
+ SQLITE_EXPECTED_NODE_COUNT = 227
+ SQLITE_EXPECTED_EDGE_COUNT = 580
+ POSTGRES_EXPECTED_NODE_COUNT = 115
+ POSTGRES_EXPECTED_EDGE_COUNT = 356

# Then use these constants in the assertions
- assert node_count == 227, f"Expected 227 nodes, got {node_count}"
- assert edge_count == 580, f"Expected 580 edges, got {edge_count}"
+ assert node_count == SQLITE_EXPECTED_NODE_COUNT, f"Expected {SQLITE_EXPECTED_NODE_COUNT} nodes, got {node_count}"
+ assert edge_count == SQLITE_EXPECTED_EDGE_COUNT, f"Expected {SQLITE_EXPECTED_EDGE_COUNT} edges, got {edge_count}"

# And later for PostgreSQL
- assert node_count == 115, f"Expected 115 nodes, got {node_count}"
- assert edge_count == 356, f"Expected 356 edges, got {edge_count}"
+ assert node_count == POSTGRES_EXPECTED_NODE_COUNT, f"Expected {POSTGRES_EXPECTED_NODE_COUNT} nodes, got {node_count}"
+ assert edge_count == POSTGRES_EXPECTED_EDGE_COUNT, f"Expected {POSTGRES_EXPECTED_EDGE_COUNT} edges, got {edge_count}"
```


Also applies to: 196-200

</blockquote></details>
<details>
<summary>evals/mem0_01042025/hotpot_qa_mem0.py (2)</summary><blockquote>

`28-28`: **Rename unused loop variable.**

The loop control variable `i` is not used within the loop body.

```diff
- for i, document in enumerate(tqdm(corpus, desc="Adding documents")):
+ for _, document in enumerate(tqdm(corpus, desc="Adding documents")):
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

28-28: Loop control variable `i` not used within loop body

Rename unused `i` to `_i`

(B007)

</details>

</details>

---

`71-72`: **Consider handling empty search results.**

The code doesn't check if the memory search returns empty results before trying to format them.

```diff
- relevant_memories = memory.search(query=question, user_id=user_id, limit=5)
- memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_memories["results"])
+ relevant_memories = memory.search(query=question, user_id=user_id, limit=5)
+ if not relevant_memories.get("results"):
+     memories_str = "No relevant context found."
+ else:
+     memories_str = "\n".join(f"- {entry['memory']}" for entry in relevant_memories["results"])
```

</blockquote></details>
<details>
<summary>evals/plot_metrics.py (2)</summary><blockquote>

`9-20`: **Robust confidence interval calculation.**

This method correctly uses `stats.t.interval` for computing the confidence interval. Consider handling edge cases like `NaN` values or infinities in `accuracies` if your data source can produce malformed values.

---

`62-175`: **Extensive logic for loading specialized metrics.**

The conditional checks for system-specific metrics (Graphiti, Mem0, Cognee) are well-structured. However, the nested conditions are somewhat verbose. You might consider centralizing the logic in a mapping dict to reduce repetitive code.  


Additionally, lines 210-210 iterate over `all_systems_metrics.keys()`. This could be streamlined:  
```diff
-for system in all_systems_metrics.keys():
+for system in all_systems_metrics:
```

</blockquote></details>
<details>
<summary>cognee/eval_framework/corpus_builder/task_getters/get_default_tasks_by_indices.py (2)</summary><blockquote>

`33-46`: **Combining base tasks with graph-related tasks.**

Returning `[graph_task, add_data_points_task]` in addition to the base tasks is straightforward and clear. If `user` is essential to these tasks, confirm its usage or future usage in subsequent references.

---

`49-58`: **Core chunking tasks plus data points addition.**

Similar to `get_no_summary_tasks`, `user` is introduced but not used within the function body. If it’s truly optional, consider documenting that in the docstring for clarity.

</blockquote></details>
<details>
<summary>evals/falkor_01042025/hotpot_qa_falkor_graphrag_sdk.py (2)</summary><blockquote>

`1-9`: **Unused import detected.**

`URL` from `graphrag_sdk.source` is never used (line 5). Consider removing it to keep imports clean:  
```diff
- from graphrag_sdk.source import URL, STRING
+ from graphrag_sdk.source import STRING
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

5-5: `graphrag_sdk.source.URL` imported but unused

Remove unused import: `graphrag_sdk.source.URL`

(F401)

</details>

</details>

---

`35-64`: **Ontology creation logic seems solid.**

The function creates an ontology from source data and writes it to JSON. Good job providing print statements for user awareness. Consider robust error handling if the corpus file has unexpected structure.

</blockquote></details>
<details>
<summary>evals/graphiti_01042025/hotpot_qa_graphiti.py (3)</summary><blockquote>

`9-9`: **Remove unused import**

The `from openai import OpenAI` import is never referenced. Removing it will keep the code clean and avoid confusion.

```diff
- from openai import OpenAI
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

9-9: `openai.OpenAI` imported but unused

Remove unused import: `openai.OpenAI`

(F401)

</details>

</details>

---

`23-24`: **Add robust file handling**

Reading JSON files without error handling can lead to runtime failures if files are missing or malformed. Safely handle file I/O and JSON parsing errors to improve resilience.

```diff
 try:
     with open(corpus_file, "r", encoding="utf-8") as file:
         corpus = json.load(file)
 except (FileNotFoundError, json.JSONDecodeError) as e:
     print(f"Error loading {corpus_file}: {e}")
     return
```


Also applies to: 61-62, 118-119

---

`94-95`: **Handle potential LLM call failures**

For calls to `llm.ainvoke`, consider adding retries or exception handling to manage rate limits, network errors, or unexpected responses from the LLM.

</blockquote></details>
<details>
<summary>cognee/modules/pipelines/operations/run_tasks_base.py (1)</summary><blockquote>

`18-42`: **Check for potential recursion depth with large tasks**

The approach of yielding results via `run_tasks_base` within an async generator is elegant. However, if there are many leftover tasks, repeated calls could accumulate recursive iterations. Ensure that the sequence of tasks is finite and that you won't exceed recursion limits or have performance bottlenecks.

</blockquote></details>

</blockquote></details>

<details>
<summary>🛑 Comments failed to post (9)</summary><blockquote>

<details>
<summary>cognee/shared/logging_utils.py (1)</summary><blockquote>

216-217: _⚠️ Potential issue_

**Critical syntax error in hasattr check.**

The `__name__` reference is incorrect - it's trying to use the value of `__name__` variable rather than checking for the string literal `"__name__"`. This will cause a NameError at runtime.

```diff
-            if hasattr(exc_type, __name__):
+            if hasattr(exc_type, "__name__"):
                event_dict["exception_type"] = exc_type.__name__
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
            if hasattr(exc_type, "__name__"):
                event_dict["exception_type"] = exc_type.__name__
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>.github/workflows/disable_independent_workflows.sh (1)</summary><blockquote>

4-4: _⚠️ Potential issue_

**Add error handling to directory change.**

The `cd` command should include error handling to prevent the script from continuing execution in the wrong directory if the command fails.

```diff
-cd "$(dirname "$0")"
+cd "$(dirname "$0")" || exit 1
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
cd "$(dirname "$0")" || exit 1
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Shellcheck (0.10.0)</summary>

[warning] 4-4: Use 'cd ... || exit' or 'cd ... || return' in case cd fails.

(SC2164)

</details>

</details>

</blockquote></details>
<details>
<summary>cognee/tests/test_telemetry.py (1)</summary><blockquote>

44-44: _⚠️ Potential issue_

**Fix misleading comment about environment setting.**

The comment suggests setting the environment to "dev" but the code sets it to "prod", which is contradictory.

```diff
- os.environ["ENV"] = "prod"  # Set to dev to ensure telemetry is sent
+ os.environ["ENV"] = "prod"  # Set to prod to ensure telemetry is sent
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        os.environ["ENV"] = "prod"  # Set to prod to ensure telemetry is sent
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>cognee/tests/test_relational_db_migration.py (1)</summary><blockquote>

221-221: _🛠️ Refactor suggestion_

**Add more detailed instructions for PostgreSQL test setup.**

The comment mentions running a SQL script but lacks specifics on where to find it or how to run it.

```diff
- # To run test manually you first need to run the Chinook_PostgreSql.sql script in the test_data directory
+ # To run this test manually:
+ # 1. Ensure PostgreSQL is running and accessible with the credentials specified below
+ # 2. Run the following command from the project root:
+ #    psql -U cognee -d test_migration_db -h 127.0.0.1 -f cognee/tests/test_data/Chinook_PostgreSql.sql
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    # To run this test manually:
    # 1. Ensure PostgreSQL is running and accessible with the credentials specified below
    # 2. Run the following command from the project root:
    #    psql -U cognee -d test_migration_db -h 127.0.0.1 -f cognee/tests/test_data/Chinook_PostgreSql.sql
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>evals/mem0_01042025/hotpot_qa_mem0.py (4)</summary><blockquote>

53-54: _🛠️ Refactor suggestion_

**Add error handling for QA pairs file.**

Similar to the corpus loading, there's no error handling for loading the QA pairs file.

```diff
- with open(qa_pairs_file, "r") as file:
-     qa_pairs = json.load(file)
+ try:
+     with open(qa_pairs_file, "r") as file:
+         qa_pairs = json.load(file)
+ except FileNotFoundError:
+     print(f"Error: QA pairs file '{qa_pairs_file}' not found.")
+     return []
+ except json.JSONDecodeError:
+     print(f"Error: QA pairs file '{qa_pairs_file}' contains invalid JSON.")
+     return []
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    try:
        with open(qa_pairs_file, "r") as file:
            qa_pairs = json.load(file)
    except FileNotFoundError:
        print(f"Error: QA pairs file '{qa_pairs_file}' not found.")
        return []
    except json.JSONDecodeError:
        print(f"Error: QA pairs file '{qa_pairs_file}' contains invalid JSON.")
        return []
`````

</details>

<!-- suggestion_end -->

---

111-136: _🛠️ Refactor suggestion_

**Add error handling in main function.**

The main function should handle potential exceptions from the pipeline to prevent crashes.

```diff
- def main(config):
-     """Main function for HotpotQA memory pipeline."""
-     print("Starting HotpotQA memory pipeline...")
-     print(f"Configuration: {config}")
-
-     # Initialize clients
-     memory = Memory()
-     openai_client = OpenAI()
-
-     # Load corpus to memory
-     load_corpus_to_memory(
-         memory=memory,
-         corpus_file=config.corpus_file,
-         user_id=config.user_id,
-         limit=config.corpus_limit,
-     )
-
-     # Answer questions
-     print(f"Answering questions from {config.qa_pairs_file}...")
-     answer_questions(
-         memory=memory,
-         openai_client=openai_client,
-         model_name=config.model_name,
-         user_id=config.user_id,
-         qa_pairs_file=config.qa_pairs_file,
-         print_results=config.print_results,
-         output_file=config.results_file,
-         limit=config.qa_limit,
-     )
-
-     print(f"Results saved to {config.results_file}")
-     print("Pipeline completed successfully")
+ def main(config):
+     """Main function for HotpotQA memory pipeline."""
+     try:
+         print("Starting HotpotQA memory pipeline...")
+         print(f"Configuration: {config}")
+
+         # Initialize clients
+         memory = Memory()
+         openai_client = OpenAI()
+
+         # Load corpus to memory
+         load_corpus_to_memory(
+             memory=memory,
+             corpus_file=config.corpus_file,
+             user_id=config.user_id,
+             limit=config.corpus_limit,
+         )
+
+         # Answer questions
+         print(f"Answering questions from {config.qa_pairs_file}...")
+         answer_questions(
+             memory=memory,
+             openai_client=openai_client,
+             model_name=config.model_name,
+             user_id=config.user_id,
+             qa_pairs_file=config.qa_pairs_file,
+             print_results=config.print_results,
+             output_file=config.results_file,
+             limit=config.qa_limit,
+         )
+
+         print(f"Results saved to {config.results_file}")
+         print("Pipeline completed successfully")
+     except Exception as e:
+         print(f"Error in pipeline execution: {e}")
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
def main(config):
    """Main function for HotpotQA memory pipeline."""
    try:
        print("Starting HotpotQA memory pipeline...")
        print(f"Configuration: {config}")

        # Initialize clients
        memory = Memory()
        openai_client = OpenAI()

        # Load corpus to memory
        load_corpus_to_memory(
            memory=memory,
            corpus_file=config.corpus_file,
            user_id=config.user_id,
            limit=config.corpus_limit,
        )

        # Answer questions
        print(f"Answering questions from {config.qa_pairs_file}...")
        answer_questions(
            memory=memory,
            openai_client=openai_client,
            model_name=config.model_name,
            user_id=config.user_id,
            qa_pairs_file=config.qa_pairs_file,
            print_results=config.print_results,
            output_file=config.results_file,
            limit=config.qa_limit,
        )

        print(f"Results saved to {config.results_file}")
        print("Pipeline completed successfully")
    except Exception as e:
        print(f"Error in pipeline execution: {e}")
`````

</details>

<!-- suggestion_end -->

---

18-38: _🛠️ Refactor suggestion_

**Add error handling for file operations.**

The function lacks error handling for file operations, which could fail if the corpus file doesn't exist or has invalid JSON.

```diff
- with open(corpus_file, "r") as file:
-     corpus = json.load(file)
+ try:
+     with open(corpus_file, "r") as file:
+         corpus = json.load(file)
+ except FileNotFoundError:
+     print(f"Error: Corpus file '{corpus_file}' not found.")
+     return memory
+ except json.JSONDecodeError:
+     print(f"Error: Corpus file '{corpus_file}' contains invalid JSON.")
+     return memory
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    print(f"Loading corpus from {corpus_file}...")
    try:
        with open(corpus_file, "r") as file:
            corpus = json.load(file)
    except FileNotFoundError:
        print(f"Error: Corpus file '{corpus_file}' not found.")
        return memory
    except json.JSONDecodeError:
        print(f"Error: Corpus file '{corpus_file}' contains invalid JSON.")
        return memory

    # Apply limit if specified
    if limit is not None:
        corpus = corpus[:limit]
        print(f"Limited to first {limit} documents")

    print(f"Adding {len(corpus)} documents to memory...")
    for i, document in enumerate(tqdm(corpus, desc="Adding documents")):
        # Create a conversation that includes the document content
        messages = [
            {"role": "system", "content": "This is a document to remember."},
            {"role": "user", "content": "Please remember this document."},
            {"role": "assistant", "content": document},
        ]
        memory.add(messages, user_id=user_id)

    print("All documents added to memory")
    return memory
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.8.2)</summary>

28-28: Loop control variable `i` not used within loop body

Rename unused `i` to `_i`

(B007)

</details>

</details>

---

98-100: _🛠️ Refactor suggestion_

**Add error handling for writing results.**

The code should handle potential errors when writing to the output file.

```diff
- with open(output_file, "w", encoding="utf-8") as file:
-     json.dump(results, file, indent=2)
+ try:
+     with open(output_file, "w", encoding="utf-8") as file:
+         json.dump(results, file, indent=2)
+ except IOError as e:
+     print(f"Error: Failed to write results to '{output_file}': {e}")
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        print(f"Saving results to {output_file}...")
        try:
            with open(output_file, "w", encoding="utf-8") as file:
                json.dump(results, file, indent=2)
        except IOError as e:
            print(f"Error: Failed to write results to '{output_file}': {e}")
`````

</details>

<!-- suggestion_end -->

</blockquote></details>
<details>
<summary>cognee/modules/pipelines/operations/run_tasks_base.py (1)</summary><blockquote>

117-169: _⚠️ Potential issue_

**Guard against a potential `None` user**

In `handle_task`, the code sends telemetry using `user.id`, but there is no check if `user` is `None`. If `user` is optional, referencing `user.id` will cause errors. Consider a safeguard or a fallback mechanism.

```diff
-        user_id=user.id,
+        user_id=user.id if user else None,
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
async def handle_task(
    running_task: Task,
    args: list,
    leftover_tasks: list[Task],
    next_task_batch_size: int,
    user: User,
):
    """Handle common task workflow with logging, telemetry, and error handling around the core execution logic."""
    # Get task information using the helper functions
    task_type = get_task_type(running_task)
    executor = get_task_executor(task_type)

    # Determine executor args based on task type
    execute_args = (args, leftover_tasks)
    if task_type in ["Async Generator", "Generator"]:
        execute_args += (next_task_batch_size,)

    logger.info(f"{task_type} task started: `{running_task.executable.__name__}`")
    send_telemetry(
        f"{task_type} Task Started",
        user_id=user.id if user else None,
        additional_properties={
            "task_name": running_task.executable.__name__,
        },
    )
    try:
        # Add user to the execute args
        complete_args = execute_args + (user,)
        async for result in executor(running_task, *complete_args):
            yield result

        logger.info(f"{task_type} task completed: `{running_task.executable.__name__}`")
        send_telemetry(
            f"{task_type} Task Completed",
            user_id=user.id if user else None,
            additional_properties={
                "task_name": running_task.executable.__name__,
            },
        )
    except Exception as error:
        logger.error(
            f"{task_type} task errored: `{running_task.executable.__name__}`\n{str(error)}\n",
            exc_info=True,
        )
        send_telemetry(
            f"{task_type} Task Errored",
            user_id=user.id if user else None,
            additional_properties={
                "task_name": running_task.executable.__name__,
            },
        )
        raise error
`````

</details>

<!-- suggestion_end -->

</blockquote></details>

</blockquote></details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T14:16:28Z
user: login: borisarzentar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T17:08:25Z
user: login: lxobr
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T18:59:53Z
user: login: borisarzentar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:21:08Z
user: login: lxobr
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-16T07:03:34Z
user: login: borisarzentar
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T07:17:21Z

Review comments:
diff_hunk: @@ -0,0 +1,186 @@
+import inspect
+from cognee.shared.logging_utils import get_logger
+from cognee.modules.users.models import User
+from cognee.shared.utils import send_telemetry
+
+from ..tasks.Task import Task
+
+logger = get_logger("run_tasks_base")
+
+
+async def execute_async_generator_task(
+    running_task: Task,
+    args: list,
+    leftover_tasks: list[Task],
+    next_task_batch_size: int,
+    user: User,
+):
+    """Execute async generator task and process results with batching."""
+    results = []
+
+    async_iterator = running_task.run(*args)
+
+    async for partial_result in async_iterator:
+        results.append(partial_result)
+
+        if len(results) == next_task_batch_size:
+            async for result in run_tasks_base(
+                leftover_tasks,
+                results,
+                user=user,
+            ):
+                yield result
+
+            results = []
+
+    if len(results) > 0:
+        async for result in run_tasks_base(leftover_tasks, results, user):
+            yield result
+
+        results = []
+
+
+async def execute_generator_task(
+    running_task: Task,
+    args: list,
+    leftover_tasks: list[Task],
+    next_task_batch_size: int,
+    user: User,
+):
+    """Execute generator task and process results with batching."""
+    results = []
+
+    for partial_result in running_task.run(*args):
+        results.append(partial_result)
+
+        if len(results) == next_task_batch_size:
+            async for result in run_tasks_base(leftover_tasks, results, user):
+                yield result
+
+            results = []
+
+    if len(results) > 0:
+        async for result in run_tasks_base(leftover_tasks, results, user):
+            yield result
+
+        results = []
+
+
+async def execute_coroutine_task(
+    running_task: Task, args: list, leftover_tasks: list[Task], user: User
+):
+    """Execute coroutine task and process single result."""
+    task_result = await running_task.run(*args)
+
+    async for result in run_tasks_base(leftover_tasks, task_result, user):
+        yield result
+
+
+async def execute_function_task(
+    running_task: Task, args: list, leftover_tasks: list[Task], user: User
+):
+    """Execute function task and process single result."""
+    task_result = running_task.run(*args)
+
+    async for result in run_tasks_base(leftover_tasks, task_result, user):
+        yield result
+
+
+def get_task_type(running_task: Task):
+    """Determine the type of task based on the executable."""
+    if inspect.isasyncgenfunction(running_task.executable):
+        return "Async Generator"
+    elif inspect.isgeneratorfunction(running_task.executable):
+        return "Generator"
+    elif inspect.iscoroutinefunction(running_task.executable):
+        return "Coroutine"
+    elif inspect.isfunction(running_task.executable):
+        return "Function"
+    else:
+        raise ValueError(f"Unsupported task type: {running_task.executable}")
+
+
+def get_task_executor(task_type: str):
+    """Get the appropriate executor function based on task type."""
+    if task_type == "Async Generator":
+        return execute_async_generator_task
+    elif task_type == "Generator":
+        return execute_generator_task
+    elif task_type == "Coroutine":
+        return execute_coroutine_task
+    elif task_type == "Function":
+        return execute_function_task
+    else:
+        raise ValueError(f"Unsupported task type: {task_type}")
+
+
+async def handle_task(
+    running_task: Task,
+    args: list,
+    leftover_tasks: list[Task],
+    next_task_batch_size: int,
+    user: User,
+):
+    """Handle common task workflow with logging, telemetry, and error handling around the core execution logic."""
+    # Get task information using the helper functions
+    task_type = get_task_type(running_task)
path: cognee/modules/pipelines/operations/run_tasks_base.py
user: login: borisarzentar
body: I see that we often use this "pattern" when we handle multiple types of data, but there is a real pattern for that.
https://en.wikipedia.org/wiki/Polymorphism_(computer_science)#Subtyping

We can solve this by moving the execution type methods to `Task` and just call `Task.run` like I did it here -> https://github.com/topoteretes/cognee/pull/690/files#diff-54f3065eb9ce36810ac0c36306830f3dffadee4efa80a9b9a5140f72f8b5feafR42

It is cleaner solution and we don't have to juggle with strings here and there.
created_at: 2025-04-14T17:07:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,186 @@
+import inspect
+from cognee.shared.logging_utils import get_logger
+from cognee.modules.users.models import User
+from cognee.shared.utils import send_telemetry
+
+from ..tasks.Task import Task
+
+logger = get_logger("run_tasks_base")
+
+
+async def execute_async_generator_task(
+    running_task: Task,
+    args: list,
+    leftover_tasks: list[Task],
+    next_task_batch_size: int,
+    user: User,
+):
+    """Execute async generator task and process results with batching."""
+    results = []
+
+    async_iterator = running_task.run(*args)
+
+    async for partial_result in async_iterator:
+        results.append(partial_result)
+
+        if len(results) == next_task_batch_size:
+            async for result in run_tasks_base(
+                leftover_tasks,
+                results,
+                user=user,
+            ):
+                yield result
+
+            results = []
+
+    if len(results) > 0:
+        async for result in run_tasks_base(leftover_tasks, results, user):
+            yield result
+
+        results = []
+
+
+async def execute_generator_task(
+    running_task: Task,
+    args: list,
+    leftover_tasks: list[Task],
+    next_task_batch_size: int,
+    user: User,
+):
+    """Execute generator task and process results with batching."""
+    results = []
+
+    for partial_result in running_task.run(*args):
+        results.append(partial_result)
+
+        if len(results) == next_task_batch_size:
+            async for result in run_tasks_base(leftover_tasks, results, user):
+                yield result
+
+            results = []
+
+    if len(results) > 0:
+        async for result in run_tasks_base(leftover_tasks, results, user):
+            yield result
+
+        results = []
+
+
+async def execute_coroutine_task(
+    running_task: Task, args: list, leftover_tasks: list[Task], user: User
+):
+    """Execute coroutine task and process single result."""
+    task_result = await running_task.run(*args)
+
+    async for result in run_tasks_base(leftover_tasks, task_result, user):
+        yield result
+
+
+async def execute_function_task(
+    running_task: Task, args: list, leftover_tasks: list[Task], user: User
+):
+    """Execute function task and process single result."""
+    task_result = running_task.run(*args)
+
+    async for result in run_tasks_base(leftover_tasks, task_result, user):
+        yield result
+
+
+def get_task_type(running_task: Task):
+    """Determine the type of task based on the executable."""
+    if inspect.isasyncgenfunction(running_task.executable):
+        return "Async Generator"
+    elif inspect.isgeneratorfunction(running_task.executable):
+        return "Generator"
+    elif inspect.iscoroutinefunction(running_task.executable):
+        return "Coroutine"
+    elif inspect.isfunction(running_task.executable):
+        return "Function"
+    else:
+        raise ValueError(f"Unsupported task type: {running_task.executable}")
+
+
+def get_task_executor(task_type: str):
+    """Get the appropriate executor function based on task type."""
+    if task_type == "Async Generator":
+        return execute_async_generator_task
+    elif task_type == "Generator":
+        return execute_generator_task
+    elif task_type == "Coroutine":
+        return execute_coroutine_task
+    elif task_type == "Function":
+        return execute_function_task
+    else:
+        raise ValueError(f"Unsupported task type: {task_type}")
+
+
+async def handle_task(
+    running_task: Task,
+    args: list,
+    leftover_tasks: list[Task],
+    next_task_batch_size: int,
+    user: User,
+):
+    """Handle common task workflow with logging, telemetry, and error handling around the core execution logic."""
+    # Get task information using the helper functions
+    task_type = get_task_type(running_task)
path: cognee/modules/pipelines/operations/run_tasks_base.py
user: login: lxobr
body: Thanks for the comment. It was outside the original scope, but worth improving.
created_at: 2025-04-15T18:59:53Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,97 @@
+from typing import Union, Callable, Any, Coroutine, Generator, AsyncGenerator
+import inspect
+
+
+class Task:
+    executable: Union[
+        Callable[..., Any],
+        Callable[..., Coroutine[Any, Any, Any]],
+        Generator[Any, Any, Any],
+        AsyncGenerator[Any, Any],
+    ]
+    task_config: dict[str, Any] = {
+        "batch_size": 1,
+    }
+    default_params: dict[str, Any] = {}
+    task_type: str = None
+    _execute_method: Callable = None
+    _next_batch_size: int = 1
+
+    def __init__(self, executable, *args, task_config=None, **kwargs):
+        self.executable = executable
+        self.default_params = {"args": args, "kwargs": kwargs}
+
+        if inspect.isasyncgenfunction(executable):
+            self.task_type = "Async Generator"
+            self._execute_method = self.execute_async_generator
+        elif inspect.isgeneratorfunction(executable):
+            self.task_type = "Generator"
+            self._execute_method = self.execute_generator
+        elif inspect.iscoroutinefunction(executable):
+            self.task_type = "Coroutine"
+            self._execute_method = self.execute_coroutine
+        elif inspect.isfunction(executable):
+            self.task_type = "Function"
+            self._execute_method = self.execute_function
+        else:
+            raise ValueError(f"Unsupported task type: {executable}")
+
+        if task_config is not None:
+            self.task_config = task_config
+
+            if "batch_size" not in task_config:
+                self.task_config["batch_size"] = 1
+
+    def run(self, *args, **kwargs):
+        """Execute the underlying task with given arguments."""
+        combined_args = args + self.default_params["args"]
+        combined_kwargs = {**self.default_params["kwargs"], **kwargs}
+
+        return self.executable(*combined_args, **combined_kwargs)
+
+    async def execute_async_generator(self, args):
+        """Execute async generator task and collect results in batches."""
+        results = []
+        async_iterator = self.run(*args)
+
+        async for partial_result in async_iterator:
+            results.append(partial_result)
+
+            if len(results) == self._next_batch_size:
+                yield results
+                results = []
+
+        if results:
+            yield results
+
+    async def execute_generator(self, args):
+        """Execute generator task and collect results in batches."""
+        results = []
+
+        for partial_result in self.run(*args):
+            results.append(partial_result)
+
+            if len(results) == self._next_batch_size:
+                yield results
+                results = []
+
+        if results:
+            yield results
+
+    async def execute_coroutine(self, args):
+        """Execute coroutine task and yield the result."""
+        task_result = await self.run(*args)
+        yield task_result
+
+    async def execute_function(self, args):
+        """Execute function task and yield the result."""
+        task_result = self.run(*args)
+        yield task_result
+
+    async def execute(self, args, next_batch_size=None):
path: cognee/modules/pipelines/tasks/task.py
user: login: borisarzentar
body: I am a bit confused by having `run` and `execute`. I expect to call `task.run` when I want to run it, but then the `execute` is actually running the task. Can we keep `.run` as the method for running the tasks?
created_at: 2025-04-15T21:21:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,97 @@
+from typing import Union, Callable, Any, Coroutine, Generator, AsyncGenerator
+import inspect
+
+
+class Task:
+    executable: Union[
+        Callable[..., Any],
+        Callable[..., Coroutine[Any, Any, Any]],
+        Generator[Any, Any, Any],
+        AsyncGenerator[Any, Any],
+    ]
+    task_config: dict[str, Any] = {
+        "batch_size": 1,
+    }
+    default_params: dict[str, Any] = {}
+    task_type: str = None
+    _execute_method: Callable = None
+    _next_batch_size: int = 1
+
+    def __init__(self, executable, *args, task_config=None, **kwargs):
+        self.executable = executable
+        self.default_params = {"args": args, "kwargs": kwargs}
+
+        if inspect.isasyncgenfunction(executable):
+            self.task_type = "Async Generator"
+            self._execute_method = self.execute_async_generator
+        elif inspect.isgeneratorfunction(executable):
+            self.task_type = "Generator"
+            self._execute_method = self.execute_generator
+        elif inspect.iscoroutinefunction(executable):
+            self.task_type = "Coroutine"
+            self._execute_method = self.execute_coroutine
+        elif inspect.isfunction(executable):
+            self.task_type = "Function"
+            self._execute_method = self.execute_function
+        else:
+            raise ValueError(f"Unsupported task type: {executable}")
+
+        if task_config is not None:
+            self.task_config = task_config
+
+            if "batch_size" not in task_config:
+                self.task_config["batch_size"] = 1
+
+    def run(self, *args, **kwargs):
+        """Execute the underlying task with given arguments."""
+        combined_args = args + self.default_params["args"]
+        combined_kwargs = {**self.default_params["kwargs"], **kwargs}
+
+        return self.executable(*combined_args, **combined_kwargs)
+
+    async def execute_async_generator(self, args):
+        """Execute async generator task and collect results in batches."""
+        results = []
+        async_iterator = self.run(*args)
+
+        async for partial_result in async_iterator:
+            results.append(partial_result)
+
+            if len(results) == self._next_batch_size:
+                yield results
+                results = []
+
+        if results:
+            yield results
+
+    async def execute_generator(self, args):
+        """Execute generator task and collect results in batches."""
+        results = []
+
+        for partial_result in self.run(*args):
+            results.append(partial_result)
+
+            if len(results) == self._next_batch_size:
+                yield results
+                results = []
+
+        if results:
+            yield results
+
+    async def execute_coroutine(self, args):
+        """Execute coroutine task and yield the result."""
+        task_result = await self.run(*args)
+        yield task_result
+
+    async def execute_function(self, args):
+        """Execute function task and yield the result."""
+        task_result = self.run(*args)
+        yield task_result
+
+    async def execute(self, args, next_batch_size=None):
path: cognee/modules/pipelines/tasks/task.py
user: login: lxobr
body: Ah yes, the classic case of: do we run it or do we execute it?
As you know .run() was the original method, it just calls the executable with stored params.
The new .execute() method came from refactoring logic that used to live around where we called .run(). So of course it adds batching and async handling in one place. Crucially, it depends on the info outside the task itself, which is the _next_batch_size argument. Which is why I named it .execute() since it doesn't merely run the task, but  does some managing and connects it to the next task.
I tried to follow our guidelines about naming functions by what they actually do. On a technical note, I am a bit concerned about renaming .execute() into .run(), since that would mean reviewing every .run() call and updating them from sync to async generators where needed.
I hope this clears the confusion. I would recommend keeping the name as is. Please let me know how you would like to proceed.
created_at: 2025-04-16T07:03:34Z
author_association: COLLABORATOR

PR comments:
user: login: pull-checklist[bot]
created_at: 2025-04-14T14:10:20Z
body: ### Please make sure all the checkboxes are checked:
- [x] I have tested these changes locally.
- [x] I have reviewed the code changes.
- [x] I have added end-to-end and unit tests (if applicable).
- [x] I have updated the documentation and README.md file (if necessary).
- [x] I have removed unnecessary code and debug statements.
- [x] PR title is clear and follows the convention.
- [x] I have tagged reviewers or team members for feedback.

author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-04-14T14:10:23Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
The changes introduce new environment variables for default user configuration and a new workflow migration process. Several configuration modules received updates including new methods and attributes for migration and LLM prompt handling. A new asynchronous task execution framework was added to refactor pipeline flows, while the search functionality now uses an updated type mapping. Additionally, multiple new evaluation pipelines for HotpotQA were implemented and obsolete files and notebooks were removed. Minor adjustments to logging, error handling, and test cases were also applied.

## Changes

| File(s) | Change Summary |
|---------|----------------|
| `.env.template` | Added `DEFAULT_USER_EMAIL` and `DEFAULT_USER_PASSWORD` under a "Default User Configuration" section. |
| `.github/README_WORKFLOW_MIGRATION.md`, <br> `.github/workflows/disable_independent_workflows.sh` | Introduced a new document for migrating test workflows and a shell script to disable independent workflows by replacing triggers. |
| `cognee-mcp/src/server.py` | Added new conditional branch in `search` to handle "GRAPH_COMPLETION" and "RAG_COMPLETION" search types. |
| `cognee/api/v1/config/config.py`, <br> `cognee/base_config.py` | Added a static method for migration DB configuration and new default user attributes sourced from environment variables. |
| `cognee/eval_framework/corpus_builder/run_corpus_builder.py`, <br> `.../task_getters/get_default_tasks_by_indices.py`, <br> `cognee/eval_framework/evaluation/deep_eval_adapter.py` | Updated signatures with additional parameters and optional types; modified conditional assignment for retrieval context. |
| `cognee/infrastructure/databases/relational/config.py`, <br> `.../vector/embeddings/get_embedding_engine.py`, <br> `.../vector/PGVectorAdapter.py` | Updated type annotations for migration DB fields, added an `endpoint` parameter for the embedding engine, and revamped data point creation to update existing entries. |
| `cognee/infrastructure/llm/config.py`, <br> `cognee/infrastructure/llm/prompts/answer_simple_question_benchmark2.txt`, <br> `.../benchmark3.txt`, <br> `.../benchmark4.txt`, <br> `.../generate_graph_prompt_guided.txt`, <br> `.../generate_graph_prompt_oneshot.txt`, <br> `.../generate_graph_prompt_simple.txt`, <br> `.../generate_graph_prompt_strict.txt` | Added a new `graph_prompt_path` attribute (with updates to `to_dict`) and introduced multiple new prompt files for QA and knowledge graph extraction. |
| `cognee/modules/data/extraction/knowledge_graph/extract_content_graph.py`, <br> `cognee/modules/pipelines/operations/run_tasks.py`, <br> `cognee/modules/pipelines/operations/run_tasks_base.py`, <br> `cognee/modules/search/methods/search.py`, <br> `cognee/modules/search/types/SearchType.py`, <br> `cognee/modules/users/methods/create_default_user.py`, <br> `cognee/modules/users/methods/get_default_user.py` | Modified search type mapping (from COMPLETION to RAG_COMPLETION), removed the old task runner in favor of a new asynchronous task execution framework, and updated default user creation to rely on configuration. |
| `cognee/shared/logging_utils.py` | Updated the exception handler to check for the `__name__` attribute before assignment. |
| `cognee/tasks/graph/extract_graph_from_data.py`, <br> `cognee/tasks/ingestion/migrate_relational_database.py` | Added `Optional` to import statements and introduced a private method to remove duplicate edges during migration. |
| `cognee/tests/test_cognee_server_start.py`, <br> `cognee/tests/test_custom_model.py`, <br> `cognee/tests/test_relational_db_migration.py`, <br> `cognee/tests/test_telemetry.py`, <br> `cognee/tests/unit/entity_extraction/regex_entity_extraction_test.py` | Added new test files for server start, relational DB migration, and telemetry; updated search type in model test; commented out regex entity extraction test logic. |
| `evals/README.md`, <br> `evals/falkor_01042025/hotpot_qa_falkor_graphrag_sdk.py`, <br> `evals/graphiti_01042025/hotpot_qa_graphiti.py`, <br> `evals/mem0_01042025/hotpot_qa_mem0.py`, <br> `evals/plot_metrics.py`, <br> `evals/requirements.txt` <br> *(and deletions: `evals/eval_swe_bench.py`, `evals/eval_utils.py`)* | Introduced comprehensive evaluation documentation and new pipelines for HotpotQA using different approaches (Falkor, Graphiti, Mem0) along with metrics plotting; removed obsolete evaluation scripts. |
| `evals/test_datasets/initial_test/natural_language_processing.txt`, <br> `evals/test_datasets/initial_test/trump.txt` | Deleted outdated test dataset files. |
| `examples/node/fetch.js`, <br> `examples/node/handleServerErrors.js`, <br> `examples/node/main.js`, <br> `examples/python/pokemon_datapoints_example.py` | Removed obsolete Node.js example files; in the Python example, modified task instantiation by removing the explicit batch size configuration. |
| `notebooks/cognee_code_graph_demo.ipynb`, <br> `notebooks/cognee_hotpot_eval.ipynb`, <br> `notebooks/hr_demo.ipynb`, <br> `notebooks/pokemon_datapoints_notebook.ipynb`, <br> `notebooks/cognee_simple_demo.ipynb` | Deleted multiple demonstration notebooks and updated the Cognee package version in one notebook from 0.1.26 to 0.1.36. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    autonumber
    participant Caller
    participant RTB as run_tasks_base
    participant HT as handle_task
    participant GT as get_task_type
    participant GE as get_task_executor
    participant Exec as Executor (async/gen/coroutine/func)
    
    Caller->>RTB: Invoke run_tasks_base(tasks, data, user)
    RTB->>HT: For each task, call handle_task(task, args, leftover, batch_size, user)
    HT->>GT: Determine task type
    GT-->>HT: Return task type (e.g. async, generator, etc.)
    HT->>GE: Fetch corresponding executor function
    GE-->>HT: Return executor for task type
    HT->>Exec: Execute task using executor function
    Exec-->>HT: Yield results/errors
    HT-->>RTB: Return task result
    RTB-->>Caller: Return aggregated results
```

```mermaid
sequenceDiagram
    autonumber
    participant User
    participant SS as specific_search
    participant CR as CompletionRetriever
    
    User->>SS: Submit search query with type "RAG_COMPLETION"
    SS->>SS: Map RAG_COMPLETION to CompletionRetriever
    SS->>CR: Invoke retrieval method
    CR-->>SS: Return search results
    SS-->>User: Deliver results
```

## Possibly related PRs
- **topoteretes/cognee#682**: Aligns with the new environment variable additions in `.env.template` for default user configuration, ensuring consistency across configuration management.

## Poem
> I'm just a bunny with a curious twist,  
> Hopping through code changes that can’t be missed.  
> With new workflows and pipelines on track,  
> And prompts and tests keeping things intact.  
> My whiskers twitch with glee in delight –  
> CodeRabbit’s changes make my heart light!  
> 🐇💻 Hop on into the future bright!

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=topoteretes/cognee&utm_content=730):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: gitguardian[bot]
created_at: 2025-04-14T14:10:23Z
body: #### ️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find [here](https://docs.gitguardian.com/platform/remediate/remediate-incidents) more information about risks.

---

<sup><sub>🦉 [GitGuardian](https://dashboard.gitguardian.com/auth/login/?utm_medium=checkruns&amp;utm_source=github&amp;utm_campaign=cr1) detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.<br/></sup></sub>

author_association: NONE
user: login: lxobr
created_at: 2025-04-15T19:26:48Z
body: I also fixed a little bug with the TaskGetters.
author_association: COLLABORATOR
user: login: soobrosa
created_at: 2025-04-16T10:56:18Z
body: Great job @lxobr !
author_association: CONTRIBUTOR