> ## 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.

# Recall Without an LLM Key

> Run remember and recall end to end with the local GLiNER demo extractor and fastembed embeddings, with no LLM API key configured at all.

A minimal guide to running the full `remember` → `recall` round trip with no LLM API key anywhere: the GLiNER demo extractor — cognee's open-source local extraction, a demo of its enterprise GLiNER extraction — builds the graph and writes the chunk summaries, and fastembed embeds them on CPU. Use it when you have no key to spend, when the data cannot leave the machine, or when you want a smoke test that proves the pipeline itself does not depend on an LLM.

## Before You Start

* Complete [Quickstart](/getting-started/quickstart) to understand basic operations
* Recommended (not required): install the GLiNER extra, which provides the local extractor's runtime up front:
  ```bash theme={null}
  pip install "cognee[gliner]"
  ```
  Without it, the first keyless `cognify()` installs that runtime itself — CPU-only PyTorch plus `gliner2`, about 200 MB to download and 800 MB on disk — before the pipeline starts. See [Installing the runtime](/python-api/cognify#installing-the-runtime)
* Nothing to install for the embedder: fastembed is a core cognee dependency
* Allow for the first-run downloads: the GLiNER model is about 750 MB and the `bge-small-en-v1.5` embedding model about 67 MB, both cached after the first run — cognee logs a `WARNING` naming the model, its size and the cache directory on a first-use download, and an `INFO` line on a cached load. See [Keyless local defaults](/setup-configuration/overview#keyless-local-defaults) for where each cache lives and how to move it
* No LLM API key is needed, and none is used — the script removes `LLM_API_KEY` and `OPENAI_API_KEY` from the environment before importing cognee
* Start from an unconfigured provider setup: cognee only reaches for the local pair when nothing else is set, so check that no `GRAPH_EXTRACTOR` or `EMBEDDING_*` value is exported in your shell or sitting in a `.env` file, which cognee reads as well
* Read [Local Setup (No API Key)](/guides/local-setup) for the `.env` form of local provider configuration, and [Embedding Providers](/setup-configuration/embedding-providers) to swap in a different fastembed model with its matching dimensions

## Code in Action

```python theme={null}
import asyncio
import os

# Make sure no key leaks in from the shell: the point is to prove the pipeline
# runs without one.
for var in ("LLM_API_KEY", "OPENAI_API_KEY"):
    os.environ.pop(var, None)

# Optional: per-turn feedback analysis skips itself without a key anyway,
# but turning it off keeps the call out of the run.
os.environ["AUTO_FEEDBACK"] = "false"

import cognee  # noqa: E402  (environment must be set before the import)
from cognee import SearchType  # noqa: E402

TEXT = (
    "Marie Curie was born in Warsaw and worked at the University of Paris. "
    "She won the Nobel Prize in Physics in 1903 with Pierre Curie and Henri Becquerel."
)


async def main():
    await cognee.prune.prune_data()
    await cognee.prune.prune_system(metadata=True)

    # remember() runs add + cognify and then improve(). Without session_ids,
    # improve() only runs the default enrichment (triplet/vector indexing) —
    # embeddings, no LLM — so it is safe to leave self_improvement on.
    await cognee.remember(TEXT, dataset_name="no_llm")

    # No query_type: with no usable LLM key this resolves to CHUNKS.
    results = await cognee.recall("Where was Marie Curie born?", datasets=["no_llm"], top_k=3)
    print(f"\ndefault ({results[0].search_type}): {len(results)} result(s)")
    for item in results:
        print("  -", item.text.replace("\n", " | "))

    # The GLiNER-built summaries are searchable too.
    results = await cognee.recall(
        "Where was Marie Curie born?",
        query_type=SearchType.SUMMARIES,
        datasets=["no_llm"],
        top_k=3,
    )
    print(f"\nSUMMARIES: {len(results)} result(s)")
    for item in results:
        print("  -", item.text.replace("\n", " | "))


if __name__ == "__main__":
    asyncio.run(main())
```

## What Just Happened

### Step 1: Clear Any Inherited API Key

```python theme={null}
# Make sure no key leaks in from the shell: the point is to prove the pipeline
# runs without one.
for var in ("LLM_API_KEY", "OPENAI_API_KEY"):
    os.environ.pop(var, None)
```

Cognee reads its LLM key from the environment, so a key exported in your shell or picked up from a `.env` file would quietly make this run an ordinary LLM run. Popping both variables first is what makes the result meaningful: everything after this line has no LLM to call.

### Step 2: Turn Off Per-Turn Feedback (Optional)

```python theme={null}
# Optional: per-turn feedback analysis skips itself without a key anyway,
# but turning it off keeps the call out of the run.
os.environ["AUTO_FEEDBACK"] = "false"

import cognee  # noqa: E402  (environment must be set before the import)
from cognee import SearchType  # noqa: E402
```

`AUTO_FEEDBACK` is the one setting this script touches, and it is an optimization rather than a requirement: per-turn feedback analysis is an LLM call of its own, but it checks for a usable LLM key before it builds a client and returns an empty analysis with a single debug line when there is none. Leaving it on would not break a keyless run; turning it off keeps the call out of the run entirely. For the two search types this script uses it is belt-and-braces either way — `CHUNKS` and `SUMMARIES` [opt out of the turn analysis](/core-concepts/sessions-and-caching#session-context-guidance-auto-feedback) whatever `AUTO_FEEDBACK` is set to — but the setting still matters the moment you add a completion search to the script. It goes in before `import cognee` because cognee reads its configuration at import time, hence the `# noqa: E402` markers on the imports.

Neither the extractor nor the embedder is configured, because cognee picks the local pair itself when nothing else is set. `GRAPH_EXTRACTOR` defaults to `auto`, which resolves to the GLiNER demo extractor when no usable LLM key is found, and embeddings resolve the same way to fastembed's `BAAI/bge-small-en-v1.5` on CPU. Cognee reads that model's vector size from the fastembed registry, so no dimension setting is needed either.

Both halves resolve on what is configured, not only on the missing key, and this script forces neither:

* The embedding fallback applies only when *no* embedding setting is configured. Any `EMBEDDING_PROVIDER`, `EMBEDDING_MODEL`, `EMBEDDING_API_KEY` or `EMBEDDING_ENDPOINT` that differs from the OpenAI default is honoured instead — including one read from a `.env` file. A leftover setting that carries its own `EMBEDDING_API_KEY` therefore keeps sending embedding requests off the machine with `LLM_API_KEY` popped; one without a key fails on authentication rather than falling back.
* Extraction behaves the same way: a `GRAPH_EXTRACTOR=llm` setting pins the LLM path, and the run then fails for want of a key.
* `COGNEE_SKIP_PREFLIGHT`, `COGNEE_SKIP_CONNECTION_TEST` and `MOCK_EMBEDDING` switch both fallbacks off, on the grounds that a partial or mocked provider config is deliberate and should be honoured rather than replaced.

Every process that resolves to the demo extractor logs one notice naming it and the enterprise GLiNER extraction behind it — informational, not a failure. If the `gliner2` package is missing, the run does not stop: cognee installs the runtime first, logging a `WARNING` with the environment, the download size and the index it uses, and starts the pipeline once `torch` and `gliner2` import. Only with `GLINER_AUTO_INSTALL=false` does it stop up front, with `KeylessExtractorNotInstalledError` and the install command from above; an install that fails raises `GlinerInstallError` with the same fix.

### Step 3: Remember the Text Locally

```python theme={null}
await cognee.prune.prune_data()
await cognee.prune.prune_system(metadata=True)

# remember() runs add + cognify and then improve(). Without session_ids,
# improve() only runs the default enrichment (triplet/vector indexing) —
# embeddings, no LLM — so it is safe to leave self_improvement on.
await cognee.remember(TEXT, dataset_name="no_llm")
```

Pruning first clears any vector collections left over from a previous embedding model — their dimensions would not match the 384 that `bge-small-en-v1.5` produces. `remember()` then ingests the text, builds the graph with GLiNER, and embeds the results with fastembed. Because no LLM task is in the pipeline, cognee also skips the first-run LLM connection probe, so the missing key never becomes an error.

If this first call takes minutes, it is the model downloads rather than a hang — the two warnings described above name what is being fetched and where it is going. Later runs load both models from those caches and log at `INFO`.

### Step 4: Recall With the Default Search Type

```python theme={null}
# No query_type: with no usable LLM key this resolves to CHUNKS.
results = await cognee.recall("Where was Marie Curie born?", datasets=["no_llm"], top_k=3)
print(f"\ndefault ({results[0].search_type}): {len(results)} result(s)")
for item in results:
    print("  -", item.text.replace("\n", " | "))
```

With no `query_type`, `recall()` normally answers with an LLM-written completion. When no usable LLM key is configured it falls back to `SearchType.CHUNKS` instead — a pure vector search that returns the matching text chunks — which is why the call works here at all. The printed `search_type` on each result reports which type actually ran.

### Step 5: Search the GLiNER Summaries

```python theme={null}
# The GLiNER-built summaries are searchable too.
results = await cognee.recall(
    "Where was Marie Curie born?",
    query_type=SearchType.SUMMARIES,
    datasets=["no_llm"],
    top_k=3,
)
print(f"\nSUMMARIES: {len(results)} result(s)")
for item in results:
    print("  -", item.text.replace("\n", " | "))
```

`SearchType.SUMMARIES` searches the per-chunk summaries rather than the raw chunks. On this run those summaries were written by GLiNER from the extracted entities and relationships, not by an LLM, and they are indexed and retrievable like any other summary. Both `CHUNKS` and `SUMMARIES` are retrieval-only search types, so requesting one explicitly stays LLM-free.

## What Still Needs an LLM

The graph, the summaries, and the two searches above run entirely on local models. Three things still do not:

* **Image, audio, and video files.** The keyless path covers text, document, and code files; media files are the one ingestion input that still needs a usable LLM, because transcription and vision descriptions have no LLM-free equivalent. `ImageLoader`, `AudioLoader`, and `VideoLoader` raise `LLMAPIKeyNotSetError` before reading the file — see [Loaders](/core-concepts/further-concepts/loaders).
* **Completion search types.** Anything ending in `_COMPLETION` — `GRAPH_COMPLETION`, `RAG_COMPLETION`, `HYBRID_COMPLETION` — retrieves context and then asks an LLM to write the answer. Requesting one without a key fails; see [Search Basics](/guides/search-basics) for the full list of types and what each returns.
* **The parts that write text with an LLM.** Per-turn feedback analysis, the session-context lessons behind "Extract session context", the distilled `session_learnings` from "Distill sessions", and the optional global context index all need a key to produce anything. Without one they decline rather than fail: the per-turn analysis returns an empty result, and [`improve()`](/core-concepts/main-operations/improve) reports those stages as `skipped` — `no_llm_configured` for distillation and the global context index, and `auto_feedback_disabled` for session-context extraction while `AUTO_FEEDBACK` is off, since that gate is checked first.

Sessions themselves do not need a key. This script keeps `remember()` sessionless for simplicity, but `remember(session_id=...)` and an explicit `improve(session_ids=[...])` complete on a keyless install: feedback weights, session Q\&A, and agent traces all run and reach the permanent graph. The default triplet and vector enrichment needs no LLM either, but it only runs when triplet embeddings are enabled — off by default, in which case that stage is skipped with `triplet_embedding_disabled` whether or not a key is present.

<Columns cols={2}>
  <Card title="Local Setup (No API Key)" icon="computer" href="/guides/local-setup">
    The `.env` form of local provider configuration, plus local-run troubleshooting.
  </Card>

  <Card title="Custom GLiNER Extraction" icon="tags" href="/guides/gliner-llm-free-cognify">
    Pass your own entity and relation labels, and measure what extraction dropped.
  </Card>

  <Card title="Embedding Providers" icon="layers" href="/setup-configuration/embedding-providers">
    Pick a different fastembed model and its matching dimensions.
  </Card>

  <Card title="Search Basics" icon="search" href="/guides/search-basics">
    Every search type, and which ones need an LLM to answer.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.