c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
310 lines
10 KiB
Python
310 lines
10 KiB
Python
from typing import Union, Callable, Any, Coroutine, Generator, AsyncGenerator
|
|
import inspect
|
|
|
|
from cognee.pipelines.types import _Drop
|
|
|
|
|
|
class BoundTask:
|
|
"""A Task with pre-bound keyword arguments, ready for pipeline chaining.
|
|
|
|
Created by calling a TaskSpec. The first positional argument (pipeline data)
|
|
is supplied at execution time by run_pipeline; all other kwargs are captured
|
|
at definition time.
|
|
|
|
Example::
|
|
|
|
extract = task(extract_graph, batch_size=20)
|
|
bound = extract(graph_model=KnowledgeGraph)
|
|
# bound.task has batch_size=20
|
|
# bound.kwargs has {"graph_model": KnowledgeGraph}
|
|
# When the pipeline runs: extract_graph(pipeline_data, graph_model=KnowledgeGraph)
|
|
"""
|
|
|
|
def __init__(self, inner_task: "Task", **kwargs):
|
|
self.task = inner_task
|
|
self.kwargs = kwargs
|
|
|
|
def __repr__(self):
|
|
name = self.task.executable.__name__
|
|
params = ", ".join(f"{k}={v!r}" for k, v in self.kwargs.items())
|
|
bs = self.task.task_config.get("batch_size", 1)
|
|
return f"BoundTask({name}({params}), batch_size={bs})"
|
|
|
|
|
|
class TaskSpec:
|
|
"""Callable wrapper returned by @task.
|
|
|
|
Calling a TaskSpec does NOT execute the function — it returns a BoundTask
|
|
that captures kwargs for later execution by run_pipeline.
|
|
|
|
Supports three usage patterns::
|
|
|
|
# As a decorator
|
|
@task(batch_size=20)
|
|
async def extract_graph(chunks, graph_model=None): ...
|
|
|
|
# As a functional wrapper
|
|
extract_graph_task = task(extract_graph_existing, batch_size=20)
|
|
|
|
# Both produce a TaskSpec. Calling it creates a BoundTask:
|
|
bound = extract_graph_task(graph_model=KnowledgeGraph)
|
|
bound = extract_graph_task(graph_model=KnowledgeGraph, batch_size=5)
|
|
|
|
# Use in a pipeline:
|
|
await run_pipeline([
|
|
classify_task(),
|
|
extract_graph_task(graph_model=KnowledgeGraph),
|
|
], data=raw_input, dataset="main")
|
|
|
|
To call the underlying function directly (for testing), use .direct()::
|
|
|
|
result = await extract_graph_task.direct(chunks, graph_model=KnowledgeGraph)
|
|
"""
|
|
|
|
def __init__(self, fn, batch_size=None, enriches=False, **default_params):
|
|
self._fn = fn
|
|
self._batch_size = batch_size
|
|
self._enriches = enriches
|
|
self._default_params = default_params
|
|
|
|
# Pre-build the base Task
|
|
self._base_task = Task(fn, batch_size=batch_size, enriches=enriches, **default_params)
|
|
|
|
# Copy function metadata for introspection
|
|
self.__name__ = fn.__name__
|
|
self.__doc__ = fn.__doc__
|
|
self.__module__ = getattr(fn, "__module__", None)
|
|
self.__wrapped__ = fn
|
|
|
|
def __call__(self, **kwargs) -> BoundTask:
|
|
"""Create a BoundTask with pre-bound kwargs.
|
|
|
|
Special kwargs:
|
|
batch_size: Override the Task's batch_size for this pipeline step.
|
|
enriches: Override the enriches flag for this pipeline step.
|
|
|
|
All other kwargs are passed to the underlying function at execution time.
|
|
"""
|
|
batch_size = kwargs.pop("batch_size", None)
|
|
enriches = kwargs.pop("enriches", None)
|
|
|
|
if batch_size is not None or enriches is not None:
|
|
inner = self._base_task.with_config(
|
|
**({"batch_size": batch_size} if batch_size is not None else {}),
|
|
**({"enriches": enriches} if enriches is not None else {}),
|
|
)
|
|
else:
|
|
inner = self._base_task
|
|
|
|
return BoundTask(inner, **kwargs)
|
|
|
|
@property
|
|
def task(self) -> "Task":
|
|
"""Access the underlying Task directly (backward compat)."""
|
|
return self._base_task
|
|
|
|
def direct(self, *args, **kwargs):
|
|
"""Call the underlying function directly (for testing/one-off use).
|
|
|
|
Returns the raw coroutine/generator/value — not a BoundTask.
|
|
"""
|
|
merged = {**self._default_params, **kwargs}
|
|
return self._fn(*args, **merged)
|
|
|
|
def __repr__(self):
|
|
bs = self._batch_size
|
|
return f"TaskSpec({self.__name__}, batch_size={bs})"
|
|
|
|
|
|
def task(fn=None, *, batch_size=None, enriches=False, **default_params):
|
|
"""Create a TaskSpec from a function.
|
|
|
|
Can be used as a decorator or as a functional wrapper::
|
|
|
|
# Decorator (with or without arguments)
|
|
@task
|
|
async def classify(data): ...
|
|
|
|
@task(batch_size=20)
|
|
async def extract(chunks, graph_model=None): ...
|
|
|
|
# Functional wrapper (for functions you don't own)
|
|
extract_task = task(extract_graph_existing, batch_size=20)
|
|
|
|
Calling the result returns a BoundTask for use in run_pipeline::
|
|
|
|
await run_pipeline([
|
|
classify(), # no extra kwargs
|
|
extract(graph_model=KnowledgeGraph), # bind config
|
|
extract(graph_model=KG, batch_size=5), # override batch_size
|
|
], data=input_data)
|
|
|
|
To call the function directly (testing): extract.direct(chunks, graph_model=KG)
|
|
"""
|
|
|
|
def decorator(func):
|
|
return TaskSpec(func, batch_size=batch_size, enriches=enriches, **default_params)
|
|
|
|
if fn is not None:
|
|
return decorator(fn)
|
|
return decorator
|
|
|
|
|
|
def task_summary(template: str):
|
|
"""Decorator that attaches a human-readable summary template to a task function.
|
|
|
|
The template should contain ``{n}`` as a placeholder for the result count.
|
|
|
|
Example::
|
|
|
|
@task_summary("Classified {n} document(s)")
|
|
async def classify_documents(data_documents):
|
|
...
|
|
"""
|
|
|
|
def decorator(func):
|
|
func.__task_summary__ = template
|
|
return func
|
|
|
|
return decorator
|
|
|
|
|
|
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
|
|
enriches: bool = False
|
|
_execute_method: Callable = None
|
|
|
|
def __init__(
|
|
self, executable, *args, task_config=None, batch_size=None, enriches=False, **kwargs
|
|
):
|
|
self.executable = executable
|
|
self.default_params = {"args": args, "kwargs": kwargs}
|
|
self.enriches = enriches
|
|
|
|
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
|
|
else:
|
|
self.task_config = {"batch_size": 1}
|
|
|
|
# Whether the executable accepts a ctx parameter.
|
|
# Used by the pipeline to decide whether to pass PipelineContext.
|
|
try:
|
|
self.accepts_ctx = "ctx" in inspect.signature(executable).parameters
|
|
except (ValueError, TypeError):
|
|
self.accepts_ctx = False
|
|
|
|
if batch_size is not None:
|
|
self.task_config["batch_size"] = batch_size
|
|
|
|
def with_config(self, **overrides) -> "Task":
|
|
"""Return a new Task with overridden config.
|
|
|
|
Example:
|
|
base = Task(extract_graph, batch_size=20, graph_model=KnowledgeGraph)
|
|
tasks = [base.with_config(batch_size=10)]
|
|
"""
|
|
batch_size = overrides.pop("batch_size", self.task_config["batch_size"])
|
|
enriches = overrides.pop("enriches", self.enriches)
|
|
merged_kwargs = {**self.default_params["kwargs"], **overrides}
|
|
return Task(
|
|
self.executable,
|
|
*self.default_params["args"],
|
|
batch_size=batch_size,
|
|
enriches=enriches,
|
|
**merged_kwargs,
|
|
)
|
|
|
|
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, kwargs, batch_size):
|
|
"""Execute async generator task and collect results in batches."""
|
|
results = []
|
|
async_iterator = self.run(*args, **kwargs)
|
|
|
|
async for partial_result in async_iterator:
|
|
if isinstance(partial_result, _Drop):
|
|
continue
|
|
results.append(partial_result)
|
|
|
|
if len(results) == batch_size:
|
|
yield results
|
|
results = []
|
|
|
|
if results:
|
|
yield results
|
|
|
|
async def execute_generator(self, args, kwargs, batch_size):
|
|
"""Execute generator task and collect results in batches."""
|
|
results = []
|
|
|
|
for partial_result in self.run(*args, **kwargs):
|
|
if isinstance(partial_result, _Drop):
|
|
continue
|
|
results.append(partial_result)
|
|
|
|
if len(results) == batch_size:
|
|
yield results
|
|
results = []
|
|
|
|
if results:
|
|
yield results
|
|
|
|
async def execute_coroutine(self, args, kwargs, batch_size):
|
|
"""Execute coroutine task and yield the result."""
|
|
task_result = await self.run(*args, **kwargs)
|
|
if isinstance(task_result, _Drop):
|
|
return
|
|
if self.enriches and task_result is None:
|
|
yield args[0] if args else None
|
|
return
|
|
yield task_result
|
|
|
|
async def execute_function(self, args, kwargs, batch_size):
|
|
"""Execute function task and yield the result."""
|
|
task_result = self.run(*args, **kwargs)
|
|
if isinstance(task_result, _Drop):
|
|
return
|
|
if self.enriches and task_result is None:
|
|
yield args[0] if args else None
|
|
return
|
|
yield task_result
|
|
|
|
async def execute(self, args, kwargs, next_batch_size=None):
|
|
"""Execute the task based on its type and yield results with the next task's batch size."""
|
|
batch_size = next_batch_size if next_batch_size is not None else 1
|
|
|
|
async for result in self._execute_method(args, kwargs, batch_size):
|
|
yield result
|