> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cognee.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# report()

> Generate a Graph Insight Report from the knowledge graph

# cognee.report()

```python theme={null}
async def report(
    datasets: Optional[Union[str, List[str]]] = "main_dataset",
    output_path: Optional[str] = "graph_report.md",
    top_n: int = 10,
    user: Optional[User] = None,
) -> str
```

## Description

Generate a **Graph Insight Report** — a Markdown overview of what a knowledge graph
actually contains: its most connected nodes, links that cross node-set boundaries,
a breakdown of how its edges got there, and a handful of suggested questions for
exploring the graph further.

The report is **read-only**. It is computed from the graph engine's `get_graph_data()`
and makes no schema or storage changes, so it is safe to run against a production
graph. Run it after [`remember()`](/python-api/remember) / [`cognify()`](/python-api/cognify)
to sanity-check a freshly built graph.

The full Markdown is returned as a string **and** written to `output_path`, unless
you pass `output_path=None` to skip the file write.

## Parameters

<ParamField path="datasets" type="Optional[Union[str, List[str]]]" default="'main_dataset'">Dataset name(s) to analyse. A single string is treated as a one-element list.</ParamField>
<ParamField path="output_path" type="Optional[str]" default="'graph_report.md'">File path for the generated Markdown report. Pass `None` to skip writing the file and only return the string.</ParamField>
<ParamField path="top_n" type="int" default="10">How many hub nodes and cross-set connections to surface.</ParamField>
<ParamField path="user" type="Optional[User]" default="None">User context for dataset access. Falls back to the default user.</ParamField>

<Note>
  Only **one** graph is analysed per call: `datasets` is resolved to the datasets you
  have read access to, and the **first** of those determines which graph is read.
  Passing several dataset names does not merge them into a combined report — call
  `report()` once per dataset instead.
</Note>

## Returns

`str` — the full Markdown report.

## What the report contains

<AccordionGroup>
  <Accordion title="🏆 Hub Nodes">
    The top `top_n` nodes ranked by a combined score of min–max-normalised **degree**
    plus **PageRank**. Ranking prefers the knowledge layer (`Entity` and `EntityType`
    nodes) and falls back to all content nodes when the graph has neither.

    The analysis graph excludes `NodeSet` container nodes and `belongs_to_set`
    membership edges, so organisational scaffolding never wins the hub ranking.
    Each entry lists the node's name, its node sets, its degree, and its PageRank score.
    A node with no node-set membership shows `—` in the set column — normal for
    graphs built without [node sets](/core-concepts/further-concepts/node-sets).
  </Accordion>

  <Accordion title="🔗 Surprising Cross-Set Connections">
    Entity-to-entity edges whose two endpoints have **different node-set membership** —
    the places where your sources actually connect to each other. Fully disjoint pairs
    (no shared set at all) rank first, then pairs are ordered by the combined PageRank
    of their endpoints.

    Membership is read from the `belongs_to_set` edges pointing at `NodeSet` nodes,
    plus the comma-separated `source_node_set` property that chunks and documents carry.
    If nothing qualifies, the section says so — add data under multiple
    [node sets](/core-concepts/further-concepts/node-sets) to populate it.
  </Accordion>

  <Accordion title="🏷️ Edge Provenance">
    A count and percentage split of how edges entered the graph:

    | Tag         | Meaning                                                                                              |
    | ----------- | ---------------------------------------------------------------------------------------------------- |
    | `EXTRACTED` | Both endpoints are `Entity` nodes — a relationship the LLM extracted from your content.              |
    | `DERIVED`   | Everything else — structural scaffolding such as chunk→entity, entity→type, or chunk→document edges. |

    <Warning>
      These tags are **derived at report time from the endpoint node types**, not read
      from the graph. Cognee stores no confidence score or provenance tag on edges, so
      treat the split as a structural summary rather than a per-edge quality signal.
    </Warning>
  </Accordion>

  <Accordion title="💡 Suggested Questions">
    Four to five short questions for exploring the graph, generated by a single LLM
    call over the first 2000 characters of the report body. This is the only LLM call
    the report makes — every other section is computed deterministically.

    Content questions ("How are X and Y related?") work directly with
    [`recall()`](/python-api/recall) / `search()`. The LLM sometimes suggests
    graph-**structure** questions instead — shortest paths, degree thresholds,
    per-node edge counts — which the completion-style search types cannot compute;
    use `SearchType.CYPHER` or `SearchType.NATURAL_LANGUAGE` for those. Questions
    about `EXTRACTED`/`DERIVED` edges cannot be answered by any search type: those
    tags exist only inside the report (see Edge Provenance above), not in the graph.
  </Accordion>
</AccordionGroup>

## Cost and fallbacks

* **One LLM call**, for the Suggested Questions section only. If it fails (no API key,
  rate limit, provider error), the failure is logged as a warning and the section
  degrades to a single generic question — `report()` does not raise.
* **PageRank** is computed with `networkx` (sparse). If it is unavailable or fails
  numerically, a warning is logged and hubs are ranked by degree alone, with PageRank
  reported as `0.0000`.
* **Empty graph**: if the graph has no nodes, the report body is
  `_Graph is empty — run cognify first._` instead of the four sections.

## Examples

```python theme={null}
import cognee

await cognee.remember("docs/handbook.pdf", dataset_name="onboarding")

# Write graph_report.md in the working directory and get the Markdown back
report_md = await cognee.report(datasets="onboarding")
print(report_md)

# Surface more hubs and connections, and write somewhere specific
await cognee.report(
    datasets="onboarding",
    output_path="reports/onboarding-graph.md",
    top_n=25,
)

# Return the Markdown only, without touching the filesystem
report_md = await cognee.report(datasets="onboarding", output_path=None)
```

From the terminal:

```bash theme={null}
cognee-cli report --datasets onboarding --top-n 25 --output reports/onboarding-graph.md
```

### Through `search()`

The same retriever is registered as [`SearchType.GRAPH_REPORT`](/python-api/search-type),
which is useful when you want the report to flow through the normal search plumbing:

```python theme={null}
from cognee import SearchType

results = await cognee.search(
    "",  # ignored — the report always covers the whole graph
    query_type=SearchType.GRAPH_REPORT,
    top_k=25,  # becomes the retriever's top_n
)
```

The query text is ignored and the result is a single-element list holding the same
Markdown string. Prefer `cognee.report()` when you want the file written for you.

## See also

* [Cognee CLI → Generate a Graph Insight Report](/cognee-cli/overview#generate-a-graph-insight-report) — the same operation from the terminal
* [SearchType](/python-api/search-type) — invoking the report through `search()`
* [Node Sets](/core-concepts/further-concepts/node-sets) — what the cross-set section groups by
