What You’ll Build
A paragraph of science history is ingested into a dataset, then a custom pipeline of three chained tasks turns it into a graph: the first reads the ingested document and asks the LLM for the people and claims it can find, the second asks which person each claim belongs to and attaches them, and the third writes the resulting objects to the graph and vector stores and prints a summary of what it stored. What comes out is a small graph ofPerson nodes, each linked to the ScientificClaim nodes it owns, with every node stamped with the content hash of the document it came from and the claim text and person names indexed for semantic search — so the script can close by asking “Who worked on gravity?” and getting an answer back from the graph it just built.
The complete runnable script is
examples/demos/custom_pipelines/custom_pipeline_single_object_example.py —
this page walks through its key moments rather than reproducing it.
Features in Play
- Custom Tasks and Pipelines —
Taskwraps each step andrun_custom_pipelineruns them in order against a dataset, feeding each task’s return value to the next - Add —
add()ingests the text into a dataset first, which is what gives the pipeline a document with a content hash to work from and a place for recall to look - DataPoints —
PersonandScientificClaimare the graph’s node types; theEmbeddableandDedupannotations decide which fields get embedded and which give a node its identity - Custom Data Models —
add_data_pointspersists the typed objects and their nesting as nodes and edges, without a cognify pass - Low-Level LLM —
LLMGateway.acreate_structured_outputruns both extraction passes against plain Pydantic response models - Recall — one
GRAPH_COMPLETIONquery scoped to the dataset proves the hand-built nodes are searchable like any other memory
What to Expect
The excerpts below come from one real run, trimmed of log lines. Two of the three tasks and the recall are live LLM calls, so the roles, claim wording, confidences, and answer vary from run to run; the shape of the output does not. The pipeline’s summary is the first thing the script prints, one block per person. Every line ends in the same twelve-charactersource_hash: all three people were extracted from the one document that was ingested, and the pipeline stamped its content hash onto each node it stored. A person with no attributed claims would show (no claims linked) instead of an indented list.
ResponseGraphEntry carrying dataset_name='science_claims', which is the proof that the custom pipeline’s nodes landed in the dataset and are reachable through the ordinary retrieval path. If this section instead shows a ResponseMarkerEntry saying memory is warming up, the nodes were stored outside a dataset and recall cannot see them.
forget(everything=True) reports the science_claims dataset that add() created, confirming the graph, its vectors, and the ingested document all lived there.
Before You Start
- Complete Quickstart to understand basic operations
- Ensure you have LLM Providers configured — two of the three tasks are live LLM calls, and the script requires
LLM_API_KEYin your.envor environment - Run it from a checkout of the cognee repo, so the
uv runcommand below resolves the repo’s dependencies - The script calls
cognee.forget(everything=True)both before and after the pipeline, so point it at a scratch instance rather than memory you want to keep — see Forget
How It Works
Stage 1: Model the Graph as Typed DataPoints
Embeddable marks the field that gets embedded and indexed for semantic search, Dedup marks the field that derives the node’s identity — so re-running the pipeline over text that mentions the same person again lands on the same node instead of a second one. The claims field is what makes this a graph rather than a table: a list of ScientificClaim objects on a Person becomes edges from that person to those claims.
Stage 2: Keep the LLM Schema Separate from the Graph Schema
DataPoint classes directly. A DataPoint carries an id, metadata, versioning and provenance fields, and a structured-output call would hand every one of them to the model to fill in — with invented ids and metadata as the result. Extracting into a plain schema and building the DataPoints from it afterwards keeps those fields under cognee’s control.
Stage 3: Extract People and Claims from the Ingested Document
extract_entities task. Because the pipeline runs against a dataset, the task receives the dataset’s Data records rather than a raw string, and reads the text back from where add() stored it. The LLM’s ExtractionResult is then turned into real Person and ScientificClaim objects, whose ids now come from their Dedup fields. Returning them as one list matters: the pipeline stamps provenance onto every DataPoint a task returns, and the content hash of the Data record the task started from is part of that stamp.
Stage 4: Attribute Each Claim to Its Author
link_claims_to_people task sorts the incoming nodes back into people and claims, then splits attribution into its own LLM call over just the names and claim texts rather than the source document. Assignments is another plain response model. The returned assignments are resolved back to the actual objects through claim_lookup and hung off each matching Person, so the task returns people whose claims lists are populated — the edges the graph will get.
Stage 5: Store the Objects and Read Their Provenance
add_data_points writes the people, the claims nested inside them, and the edges between them — plus the embeddings for every Embeddable field. Because the task runs inside a dataset pipeline, the nodes land in that dataset’s graph and vector stores and are attributed to it, which is what lets recall find them later. The summary then reads source_content_hash back off each stored person: the same twelve characters on every line, because every node traces back to the one document that was ingested.
Stage 6: Ingest, Then Run the Pipeline Against the Dataset
add() does the ingestion work cognify would otherwise build on: it creates the science_claims dataset and stores the text as a Data record with a content hash. run_custom_pipeline then runs the three Task objects in order inside that dataset’s context — each task’s return value becomes the next task’s first argument, and every document in the dataset goes through the chain. Running the pipeline against a dataset is also what gives the tasks a pipeline context with a dataset in it, so the stored nodes belong somewhere. The pipeline_name labels the run for logging and status tracking.
Stage 7: Recall from the Hand-Built Graph
GRAPH_COMPLETION traverses the Person and ScientificClaim nodes in the science_claims dataset exactly as it would traverse nodes produced by cognify(), which is the point of writing typed DataPoint models: custom extraction buys you a custom schema without giving up the ordinary retrieval path.
Run It
Adapting It to Your Data
The three-task shape generalizes to any typed extraction: define theDataPoint classes your domain actually has, annotate the field that should be searchable with Embeddable and the field that identifies a record with Dedup, then nest one model inside another wherever you want an edge. Give each DataPoint a matching plain Pydantic extraction model and build the DataPoints from what the LLM returns, as Stage 3 does. What changes per domain is the two system prompts and the response models; the pipeline around them stays as it is. To run it over a real corpus, add() the documents into the dataset instead of one string — every Data record in the dataset goes through the three tasks, and each stored node carries the hash of the document it came from.
Custom Tasks and Pipelines
Writing your own tasks and running them as a pipeline, step by step.
DataPoints
How
Embeddable, Dedup, and nested models shape the graph a DataPoint becomes.Custom Data Models
More on
add_data_points and modeling your own node types.Low-Level LLM
Calling
acreate_structured_output directly, including Pydantic response models.