> ## 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 local GLiNER extraction 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: GLiNER2 extracts 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
* Install the two extras this script needs — GLiNER for extraction, fastembed for embeddings:
  ```bash theme={null}
  pip install "cognee[gliner]" "fastembed<=0.8.0"
  ```
* Allow for the first-run downloads: the GLiNER model is about 800 MB and the `bge-small-en-v1.5` embedding model about 130 MB, both cached after the first run
* 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
* 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)

os.environ.update(
    {
        "GRAPH_EXTRACTOR": "gliner",
        "EMBEDDING_PROVIDER": "fastembed",
        "EMBEDDING_MODEL": "BAAI/bge-small-en-v1.5",
        "EMBEDDING_DIMENSIONS": "384",
        "EMBEDDING_MAX_COMPLETION_TOKENS": "512",
        # Per-turn feedback analysis is an LLM call; without it recall is LLM-free.
        "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: on the gliner backend this is 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: Select the Local Extractor and Embedder

```python theme={null}
os.environ.update(
    {
        "GRAPH_EXTRACTOR": "gliner",
        "EMBEDDING_PROVIDER": "fastembed",
        "EMBEDDING_MODEL": "BAAI/bge-small-en-v1.5",
        "EMBEDDING_DIMENSIONS": "384",
        "EMBEDDING_MAX_COMPLETION_TOKENS": "512",
        # Per-turn feedback analysis is an LLM call; without it recall is LLM-free.
        "AUTO_FEEDBACK": "false",
    }
)

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

`GRAPH_EXTRACTOR=gliner` swaps cognify's LLM task list for the GLiNER one, which extracts entities and relationships and writes each chunk's summary locally instead of prompting a model. Both halves have to be local: leaving `EMBEDDING_PROVIDER` at its default would send embedding requests to OpenAI and fail without a key. `EMBEDDING_DIMENSIONS` and `EMBEDDING_MAX_COMPLETION_TOKENS` describe `bge-small-en-v1.5` — 384-dimensional vectors and a 512-token input limit — so cognee sizes its vector collections and chunks correctly. The whole block runs before `import cognee` because cognee reads this configuration at import time, hence the `# noqa: E402` markers on the imports.

### 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 configured above. `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.

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

```python theme={null}
# No query_type: on the gliner backend this is 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. Two things still do not:

* **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.
* **Per-turn feedback analysis.** `AUTO_FEEDBACK` is disabled above because session feedback is itself an LLM call. For the same reason, `remember()` here is left without `session_ids`, so [`improve()`](/core-concepts/main-operations/improve) only runs the default triplet and vector enrichment.

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