Skip to main content

cognee.validate()

Description

Cross-check the graph and vector stores of a dataset and report where they disagree. validate() answers three questions no other call answers: does every edge still point at nodes that exist, does every deduplicated node still carry the id its own dedup contract derives, and is every embeddable node actually present in its vector collection. The check is read-only β€” it never writes to any store, so it is safe to run against a production dataset. It is also backend-agnostic: it is driven entirely through GraphDBInterface.get_graph_data() and VectorDBInterface.retrieve(), so it works unmodified against every supported graph and vector backend without adapter-specific code. Run it after cognify(), after a large import, or after a migration β€” the moments when the graph and the vector index can drift apart. Only one graph is checked per call: every name in dataset must resolve to a dataset you have read permission on, and the first of those determines which graph and vector store are read. Passing several dataset names does not merge them into a combined report β€” call validate() once per dataset instead. A partially authorized list is rejected as a whole β€” dataset=["mine", "not-mine"] raises rather than silently validating mine alone β€” and the default dataset="main_dataset" raises too when that dataset does not exist for you yet. This does not depend on ENABLE_BACKEND_ACCESS_CONTROL: the rejection is the same with access control on or off. Note that names resolve only within datasets you own, so a dataset merely shared with you is rejected when requested by name. Passing dataset=None (or an empty list) still skips dataset resolution entirely β€” the only path that enters no dataset context. With backend access control disabled that reads the unscoped shared stores; with it enabled the call fails, since a dataset is required to resolve the per-dataset databases.
validate() fails closed on a dataset it cannot read. If any of the given names does not resolve to a dataset you have read permission on, the call raises DatasetNotFoundError β€” message "Dataset not found or not readable.", importable from cognee.modules.data.exceptions β€” before any graph or vector adapter is opened. There is no fallback to the default or shared stores, so a report that comes back is always a report about the dataset you asked for.

Parameters

Optional[Union[str, List[str]]]
default:"'main_dataset'"
Dataset name(s) to validate. A single string is treated as a one-element list.
Optional[User]
default:"None"
User context for dataset access. Falls back to the default user.

Returns

ValidationReport β€” a Pydantic model with three fields: status is derived from the severities present, not from issue counts:
  • any error-severity issue β†’ unhealthy
  • otherwise, any warning-severity issue β†’ degraded
  • no issues at all β†’ healthy
validate, ValidationReport, ValidationIssue, and ValidationStatus are importable from the cognee top level; the IssueSeverity and IssueType enums are available from cognee.api.v1.validate. All three enums (ValidationStatus, IssueSeverity, IssueType) subclass str, so comparing against plain strings (report.status == "healthy") works without importing them β€” but printing a member shows the enum repr (ValidationStatus.HEALTHY), so use .value when you want the plain string.

What is checked

An edge whose source or target id is not in the graph’s node set. Traversal that reaches such an edge hits a dead end, so graph-based search silently loses the connection.The detail names the edge as source -[relationship]-> target along with the id(s) that are missing. Typically the result of nodes being removed without their edges; re-running cognify() for the affected dataset, or deleting and re-ingesting the source data, rebuilds a consistent edge set.
A node of a type that declares identity_fields β€” Entity and EntityType, both keyed on name β€” whose id does not equal the id its own class derives from its properties via Type.id_for(...).Cognee’s dedup contract is that two nodes with the same identity value are the same node because they hash to the same id. A node that reached the graph without going through that contract (a raw write from an importer or a migration, or a node written by an older Cognee version) can carry a stale or arbitrary id β€” which means a second, correctly-derived node for the same entity can coexist as an undetected duplicate. Hence a warning rather than an error: nothing is broken yet, but deduplication is no longer guaranteed for that node.The check is skipped for a node whose identity field is absent from its graph properties β€” the id cannot be recomputed, and the node may simply predate the field. To bring mismatched nodes onto the current scheme, re-cognify the affected datasets from scratch so their ids are derived by the models.
A node of a type that declares index_fields β€” Entity (collection Entity_name) and DocumentChunk (collection DocumentChunk_text) β€” with no matching point in its {type}_{index_field} vector collection.The node exists in the graph but is unreachable by every embedding-based search type, silently: semantic search cannot surface it and cannot use it to seed graph traversal. Re-running cognify() for the dataset re-indexes the missing nodes. If an entire node type reports one issue per node, the collection itself is likely missing or empty rather than individual points having been lost.
The checked types are read from the real model classes’ identity_fields / index_fields metadata rather than being hardcoded, so a change to either contract is picked up here automatically.

Cost and when to run

  • No LLM calls and no writes. The cost is entirely database reads.
  • The graph is read in full through get_graph_data(), so time and memory scale with the total number of nodes and edges β€” not with a sample. There is no sampling or limit parameter.
  • Vector lookups are one batched retrieve() per collection (at most two: Entity_name and DocumentChunk_text), not one call per node.
  • Because it is read-only, it is safe against production stores β€” but on a very large dataset prefer a low-load window, and note that the number of issues is unbounded: a badly drifted graph can return one issue per affected node or edge.
Good moments to run it: after cognify() or a bulk import, after applying migrations, as a pre-flight check in CI, or on a schedule as integrity monitoring.

Examples

Acting on the status β€” for example, failing a CI pre-flight check on errors while letting warnings through:
Grouping issues by type to decide what to remediate:

Over HTTP

The same check is exposed as a read-only endpoint on the API server:
The 503 is the single detail to plan for when wiring this into a health probe or a CI check: an unhealthy report is returned with a 503 status code, not a 200. Clients that raise on non-2xx responses will treat a successful-but-failing validation as a transport error, and a load balancer pointed at this path will pull the instance out of rotation on a data-integrity finding. Read the response body to tell the two apart β€” a 503 from this endpoint still carries the full report JSON, while a 500 carries {"status": "error", "reason": ...}. The endpoint wraps every exception into that 500 β€” an unreadable or unknown dataset name is a server error here, never a 404 β€” so if you alert on rejected datasets, match the reason for Dataset not found or not readable. rather than watching for a 4xx status code.

See also

  • report() β€” the other read-only diagnostic: what the graph contains, rather than whether it is consistent
  • cognify() β€” the pipeline that writes the nodes, edges, and vector points this call cross-checks
  • DataPoints β€” identity_fields, index_fields, and the id_for() contract the identity check verifies
  • run_migrations() β€” apply pending schema migrations; validating afterwards confirms the stores still agree