---
title: "Give an Agno agent a local knowledge base, with no vector database server"
date: "2026-10-29"
dateModified: "2026-10-29"
author: "Pierre Brunelle"
tags:
  - Agno
  - RAG
  - LangChain
  - Embedding Indexes
  - Local AI
description: "An Agno agent searches notes stored in a Pixeltable table. sentence-transformers embeds them on the machine, and the embedding index stays on the table. No embedding API key and no vector database server."
url: "https://www.pixeltable.com/blog/agno-agent-pixeltable-knowledge-base-local-rag"
---

# Give an Agno agent a local knowledge base, with no vector database server

An Agno agent can search notes that live in a Pixeltable table on your machine. sentence-transformers embeds the chunks locally, Pixeltable stores each chunk with its vector, and Agno reads them through its LangChain retriever. The run needs no embedding API key and no vector database server. I ran [Agno's cookbook](https://github.com/agno-agi/agno/blob/main/cookbook/07_knowledge/05_integrations/rag/local_rag_langchain_pixeltable.py) on pixeltable 0.7.21, with agno 3.1.12 and [langchain-pixeltable](https://github.com/pixeltable/langchain-pixeltable) 0.1.2. The search returned three chunks, and with Ollama serving `qwen2.5:latest` the agent answered from them.

 
If you already run a vector database you want to keep, keep it. If you do not want a schema, Pixeltable is the wrong tool. Pixeltable replaces the vector database in this stack. If the agent is already written in Agno, keep it, and leave the agent code as it is. A Pixeltable app that is not wrapping another framework declares the index on a `TableModel` and skips LangChain. That is the last section, and it is what we tell coding agents to do. The longer comparison is [Pixeltable vs LangChain for RAG](/blog/pixeltable-vs-langchain-rag-comparison).

 
## Install the packages the cookbook names

 
Agno lists the example as LangChain + Pixeltable in the [cookbook index](https://github.com/agno-agi/agno/blob/main/cookbook/07_knowledge/05_integrations/rag/README.md). The file's install line is `uv pip install langchain-pixeltable langchain-huggingface langchain-text-splitters sentence-transformers ollama openai`. I installed that set with pip, and pinned pixeltable, agno, and langchain-pixeltable to the versions that produced the output below.

 
```bash
pip install 'pixeltable==0.7.21' 'agno==3.1.12' 'langchain-pixeltable==0.1.2' langchain-huggingface langchain-text-splitters sentence-transformers ollama openai
```

 
`openai` is on that line because `from agno.models.ollama import Ollama` imports Agno's OpenAI client. This agent never calls OpenAI. Retrieval does not call a model API. The embeddings are `sentence-transformers/all-MiniLM-L6-v2`, on the machine.

 
If you want the agent's answer, install Ollama from [ollama.com](https://ollama.com). The `ollama` pip package is only the Python client, so the server has to run on its own. Retrieval works without either.

 
```bash
ollama serve
```

 
In a second terminal, pull the model while the server runs.

 
```bash
ollama pull qwen2.5:latest
```

 
## Write the chunks into agno_rag/docs

 
The cookbook keeps a notes string in the file, so the run does not fetch a web page. It splits that string at 500 characters with 40 characters of overlap, embeds the chunks, and writes them to `agno_rag/docs`. Run the file. The imports sit at the top. Here is the part that stores the rows.

 
```python
source = """
Pixeltable is a multimodal data layer. A table can hold text, images, video,
and audio together. An embedding index on a column stays up to date when rows
are inserted, updated, or deleted, so the vectors do not drift from the rows
they describe.

The langchain-pixeltable package exposes that table as a LangChain vector
store. PixeltableVectorStore writes each chunk into the table and adds an
embedding index on the stored vectors. A similarity search embeds the query
and ranks rows with Pixeltable's similarity expression.

Agno searches the store through LangChainVectorDb. Wrap the Pixeltable
retriever in Knowledge and give that knowledge base to an Agent with
search_knowledge enabled. The agent calls the knowledge search tool, and the
tool invokes the Pixeltable retriever.

This stack runs locally. sentence-transformers computes embeddings on the
machine, and Pixeltable stores the table in an embedded Postgres database.
No embedding API key and no standalone vector database server are required.
"""

table_name = "agno_rag/docs"
notes = Document(page_content=source, metadata={"source": "pixeltable-notes"})
chunks = RecursiveCharacterTextSplitter(
 chunk_size=500, chunk_overlap=40
).split_documents([notes])
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")

pxt.drop_table(table_name, if_not_exists="ignore")
pxt.create_dir("agno_rag", if_exists="ignore")

vector_store = PixeltableVectorStore(table_name=table_name, embedding=embeddings)
vector_store.add_documents(chunks)
```

 
On a fresh directory, that printed:

 
```text
Created directory 'agno_rag'.
Created table 'docs'.
Inserted 3 rows with 0 errors in 0.01 s (282.54 rows/s)
```

 
LangChain computes the vectors before the insert, which is why that line says 0.01 s. `PixeltableVectorStore` writes each chunk and its vector into the table. The first `add_documents` creates the table and a cosine embedding index on the `embedding` column, then inserts the rows. On this run the index was named `idx0` and the table held 3 rows. `pxt count agno_rag/docs` prints `3`.

 
In 0.7.21, `pxt describe agno_rag/docs` and `pxt idxs agno_rag/docs` raise `KeyError: <Type.ARRAY: 6>` on this index. The index stores precomputed vectors and has no embedding function. The count above is the check that works.

 
Pixeltable keeps the table in an embedded Postgres database under the Pixeltable home directory. The default is `~/.pixeltable`, unless `PIXELTABLE_HOME` is set. The index follows those stored vectors when a row is inserted, updated, or deleted, so the vectors stay with the rows. A later edit to the text, made without a new vector, does not re-embed the text. LangChain wrote the vector. The `TableModel` section is the one where the embedding function belongs to the index.

 
This cookbook table is text, a vector, and metadata. A Pixeltable table can also hold images, video, and audio. The [embedding index docs](https://docs.pixeltable.com/platform/embedding-indexes) show an index on those columns.

 
## Pass the retriever through Knowledge

 
```python
retriever = vector_store.as_retriever(search_kwargs={"k": 3})

knowledge = Knowledge(
 name="Pixeltable notes",
 description="Local notes stored in a Pixeltable embedding index",
 vector_db=LangChainVectorDb(knowledge_retriever=retriever),
 max_results=3,
)

agent = Agent(
 model=Ollama(id="qwen2.5:latest"),
 knowledge=knowledge,
 description="Answer the user question from the knowledge base",
 markdown=True,
 search_knowledge=True,
)
```

 
Agno's search tool calls `retrieve()` on the knowledge base. `LangChainVectorDb` implements `search()`, and `Knowledge.retrieve()` turns the call into that search. Constructing `Knowledge` also calls `exists()`. The wrapper does not implement it, logs this warning, and leaves the table in place:

 
```text
WARNING LangChainKnowledgeBase.exists() not supported - please check the vectorstore manually.
```

 
That warning is expected. The insert has already succeeded.

 
## The search returned three chunks

 
The question in the file is "How does an Agno agent search documents stored in Pixeltable?" `knowledge.search` returned three chunks, in this order:

 
```text
[1] Agno searches the store through LangChainVectorDb. Wrap the Pixeltable retriever in Knowledge and give that knowledge base to an Agent with search_knowledge enabled. The agent calls the knowledge search tool, and the tool invokes the Pixeltable retriever. This stack runs locally. sentence-transformers computes embeddings on the machine, and Pixeltable stores the table in an embedded Postgres database. No embedding API key and no standalone vector database server are required.
[2] The langchain-pixeltable package exposes that table as a LangChain vector store. PixeltableVectorStore writes each chunk into the table and adds an embedding index on the stored vectors. A similarity search embeds the query and ranks rows with Pixeltable's similarity expression.
[3] Pixeltable is a multimodal data layer. A table can hold text, images, video, and audio together. An embedding index on a column stays up to date when rows are inserted, updated, or deleted, so the vectors do not drift from the rows they describe.
```

 
The top chunk is the one that describes the wrapper. With Ollama serving `qwen2.5:latest`, the same script called the knowledge tool and printed a reply. The tool call was `search_knowledge_base(query=Agno agent search Pixeltable documents)`. The reply header said 67.5s. That was this CPU.

 
```text
To search documents stored in Pixeltable using an Agno agent, the process involves the following steps:

 1 Wrap the Pixeltable Retriever in Knowledge: The Pixeltable retriever is wrapped within a knowledge base.
 2 Enable Search Knowledge in the Agent: An Agent is configured with search_knowledge enabled. This allows the agent to call the knowledge search tool.
 3 Invoke the Knowledge Search Tool: The knowledge search tool then invokes the Pixeltable retriever to perform the search.
 4 Local Execution: This entire stack runs locally. The embeddings are computed on the machine, and Pixeltable stores the table in an embedded Postgres database. No embedding API key or standalone vector database server is required.
 5 LangChainVectorStore and PixeltableVectorStore: The langchain-pixeltable package exposes the Pixeltable as a LangChain vector store. PixeltableVectorStore writes each chunk into the table and adds an embedding index on the stored vectors. A similarity search embeds the query and ranks rows using Pixeltable's similarity expression.
 6 Multimodal Data Layer: Pixeltable is described as a multimodal data layer, capable of holding text, images, video, and audio together. The embedding index on a column remains updated as rows are inserted, updated, or deleted, ensuring that vectors do not drift from the rows they describe.
```

 
The reply follows the three chunks. It also says `LangChainVectorStore`, which is not the class. The class is `PixeltableVectorStore`.

 
## Declare the index on the model

 
For an app that is not already on Agno, put the embedding function on the index. An update to `text` then recomputes the vector. This is a different table from `agno_rag/docs`. Save it as `app.py`.

 
```python
import pixeltable as pxt
from pixeltable.functions.huggingface import sentence_transformer

TableModel = pxt.model_base()
embed = sentence_transformer.using(model_id='sentence-transformers/all-MiniLM-L6-v2')

class Notes(TableModel, name='notes'):
 text: pxt.String
 __indexes__ = [pxt.EmbeddingIndex(text, string_embed=embed)]
```

 
`-f` skips the confirmation. `pxt init` once per project, then:

 
```bash
pxt schema update app.py notes_kb -f
```

 
```text
created notes_kb/notes
```

 
Run the rest in a Python shell from the same folder. `pxt schema update` loaded `app.py` in its own process, so the shell needs the models imported before it binds them.

 
```python
from app import Notes, TableModel

TableModel.bind_all('notes_kb')
Notes.insert([
 {'text': 'Pixeltable keeps an embedding index on the table. Inserts, updates, and deletes refresh it.'},
 {'text': 'Agno agents search that index through a LangChain retriever. No vector database server is required.'},
])
sim = Notes.text.similarity(string='How does the agent search the notes?')
rows = Notes.select(Notes.text, score=sim).order_by(sim, asc=False).limit(2).collect()
for row in rows:
 print(f"{row['score']:.4f} {row['text']}")
```

 
```text
Inserted 2 rows with 0 errors in 0.64 s (3.15 rows/s)
0.2214 Agno agents search that index through a LangChain retriever. No vector database server is required.
0.0771 Pixeltable keeps an embedding index on the table. Inserts, updates, and deletes refresh it.
```

 
`pxt idxs notes_kb/notes` printed:

 
```text
/notes_kb/notes idx0 embedding text cosine sentence_transformer(text, model_id='sentence-transformers/all-MiniLM-L6-v2', normalize_embeddings=False)
```

 
Built-in model calls are computed columns, and the list is the [ecosystem page](https://docs.pixeltable.com/integrations/frameworks). Agno is not one of those calls. This post is the cookbook path. If you want a trace of the insert, [Tracing multimodal AI pipelines with OpenTelemetry](/blog/tracing-multimodal-ai-pipelines-opentelemetry) shows how to send it to Langfuse, Phoenix, or MLflow.
## Frequently asked questions

### How do I give an Agno agent a local knowledge base without a vector database server?

Install pixeltable, agno, and langchain-pixeltable. Embed the chunks locally with sentence-transformers, write them with PixeltableVectorStore into a table such as agno_rag/docs, and pass that retriever to the agent through Knowledge and LangChainVectorDb. Pixeltable stores the table in embedded Postgres under the Pixeltable home directory.

### Why does creating the Agno knowledge base print an exists() warning?

Agno calls retrieve() on Knowledge. LangChainVectorDb implements search(), and Knowledge.retrieve() turns the call into that search. Constructing Knowledge also calls exists(), which the wrapper does not implement. It logs the warning and leaves the Pixeltable table in place.

### Does the Ollama agent need an OpenAI API key?

The model call does not. Importing agno.models.ollama still imports Agno's OpenAI client, so the openai package has to be installed. Retrieval does not call OpenAI or Ollama.

