Knowledge (RAG)
beam_knowledge adds retrieval-augmented generation. You ingest text, it is split into chunks,
embedded, and stored. When knowledge is attached to RiverBeam, every request retrieves the closest
chunks to its text and prepends them to the conversation as a system message.
Attach knowledge to the pipeline
The factory reads the environment and picks an embedder and a vector store:
use std::sync::Arc;
use beam_knowledge::{build_knowledge_from_env, KnowledgeScope};
let knowledge = Arc::new(build_knowledge_from_env(1536).await?);
knowledge
.ingest(
"Riverbeam runs agents behind an API.",
None, // document_id: a new UUID when None
Some(KnowledgeScope::system()),
None, // agent_id
None, // user_id
Some("intro.md".into()), // source
)
.await?;
beam.set_knowledge(knowledge);
On each request, the pipeline retrieves the top 4 chunks for the text of the request and renders them like this, ahead of the history:
Relevant knowledge:
[1] Riverbeam runs agents behind an API.
[2] …
The block is added only when there are hits.
Choosing an embedder and store
build_knowledge_from_env(embedding_dim) reads these variables:
| Variable | Values | Default |
|---|---|---|
KNOWLEDGE_EMBEDDER | openai, nvidia, mock, hash | Auto: nvidia when an API key is set and the base URL contains nvidia, openai when a key is set, otherwise hash |
KNOWLEDGE_VECTOR_STORE | pgvector, inmemory (or memory) | pgvector |
KNOWLEDGE_EMBEDDING_DIM | a positive integer | the embedding_dim argument |
KNOWLEDGE_EMBEDDER_MODEL | model name | nvidia/llama-3.2-nv-embedqa-1b-v2 for nvidia, otherwise text-embedding-3-small |
BEAM_API_KEY | API key for the HTTP embedders | none |
BEAM_API_BASE_URL | OpenAI-compatible base URL | https://integrate.api.nvidia.com/v1 |
BEAM_DATABASE_URL | Postgres for pgvector | none |
Older fallback names are also read; see Fallback names.
The outcome:
| Situation | Embedder | Store |
|---|---|---|
| No API key | HashEmbedder (128 dimensions, no network) | In-memory. The hash embedder is never paired with pgvector. |
API key, database URL, vector extension available, feature postgres | OpenAiCompatEmbedder | PgVectorStore |
API key, but no database URL, no vector extension, or no postgres feature | OpenAiCompatEmbedder | In-memory, with a warning |
Configuration errors stop startup:
| Error | Cause |
|---|---|
BEM-231 | KNOWLEDGE_EMBEDDER is openai or nvidia without an API key |
BEM-233 | The embedder's width differs from KNOWLEDGE_EMBEDDING_DIM (pgvector only) |
BEM-234 | pgvector storage with a dimension other than 1536 |
Enable the Postgres store with the crate feature:
beam_knowledge = { path = "../riverbeam/crates/beam_knowledge", features = ["postgres"] }
Building it yourself
Skip the factory when you want explicit control:
use std::sync::Arc;
use beam_knowledge::{HashEmbedder, InMemoryVectorStore, KnowledgeManager, KnowledgeScope};
let manager = KnowledgeManager::new(
Arc::new(HashEmbedder::new(128)),
Arc::new(InMemoryVectorStore::new()),
);
let result = manager
.ingest(&document_text, None, Some(KnowledgeScope::system()), None, None, None)
.await?;
let hits = manager.retrieve("what is the capital of France", 1, None).await?;
| Type | Role |
|---|---|
Embedder | Turns text into vectors. HashEmbedder (hermetic), MockEmbedder (hash-based at any width, for tests), OpenAiCompatEmbedder::openai(...) / ::nvidia(...) (calls POST {base_url}/embeddings) |
VectorStore | upsert, query, delete. InMemoryVectorStore (cosine) and PgVectorStore (feature postgres) |
Chunker | SimpleChunker: splits on blank lines and packs paragraphs up to 800 characters by default |
KnowledgeBuilder | Chunk, embed and upsert; use with_chunker to change chunking |
KnowledgeManager | Ingest, delete_document, retrieve; what the pipeline uses |
RagContextStrategy | Renders hits into the context block; the header is configurable with new(header) |
Ingest
ingest(text, document_id, scope, agent_id, user_id, source) returns an IngestionResult with the
document_id, chunk_count, embedder_name, and stats. Ingesting with an existing
document_id replaces that document's chunks. Empty input fails with BEM-230.
stats is an IngestStats: chunk count, total, mean, minimum and maximum tokens per chunk, and
duplicate chunks. With the polars-ingest feature the statistics are computed with Polars;
otherwise a pure-Rust pass gives the same values.
Retrieve
retrieve(query, k, filter) returns up to k Hit values with text, score, document_id,
chunk_index, source and scope. A RetrievalFilter narrows by scope, agent_id, user_id
and document_ids.
Through the worker
Two worker tasks wrap the manager, so an API process can manage documents over NATS:
| Task | Payload | Result |
|---|---|---|
ingest_knowledge_document | {"text": "…", "document_id": "<uuid>?", "source_url": "…"?} | IngestionResult |
delete_knowledge_document | {"document_id": "<uuid>"} | {"deleted": <count>} |
Ingest takes inline text only (BEM-004 otherwise). Both fail with BEM-045 when the worker has no
knowledge attached.
Storage
PgVectorStore::connect creates the vector extension and three tables in ref_beam:
agent_knowledge_document, agent_knowledge_chunk (vector(1536) with an HNSW cosine index), and
agent_memory. If the extension is not available, connect returns None and the factory falls
back to the in-memory store. See Database schema.
Current limitations
The pipeline retrieves without a filter, so every request can see every ingested chunk, whatever its
scope, agent or user. Do not ingest documents that only some users may read into a knowledge store
attached to RiverBeam.
- The factory's
hashembedder always has 128 dimensions and ignoresKNOWLEDGE_EMBEDDING_DIM. - The default base URL is NVIDIA's, even when
KNOWLEDGE_EMBEDDER=openai. SetBEAM_API_BASE_URLfor OpenAI. - Unrecognized values fall back silently: an unknown
KNOWLEDGE_EMBEDDERauto-detects, and an unknownKNOWLEDGE_VECTOR_STOREmeanspgvector. PgVectorStore::queryapplieskbefore thedocument_idsfilter, so a filtered query can return fewer thankhits. Itsagent_idanduser_idfilters also match rows where those columns are empty.KnowledgeManageralways uses the default chunker.