Installation
Python 3.13 or newer. requires-python enforces it, so an older
interpreter gets a clear resolution error rather than a runtime one.
The base install
uv add redstring
pip install redstring
That gives you the whole library except two adapters: the in-memory
GraphStore and VectorStore, FakeLlmProvider, the extraction pipeline,
consolidation, temporal inference, the events, the aggregates and both
projections. It is enough to run the entire test suite and every example in
these docs.
The extras
| Extra | Adds | Install when |
|---|---|---|
llm |
LangChainLlmProvider, via langchain-core and langchain-openai |
you want a real model rather than FakeLlmProvider |
neo4j |
Neo4jGraphStore, via the neo4j driver |
you want the graph in Neo4j rather than in memory |
pgvector |
PgVectorStore, via asyncpg |
you want vectors in Postgres rather than in memory |
redis |
RedisCache, via redis[hiredis] |
you want the model cache shared between processes |
all |
all four | — |
uv add "redstring[llm]"
uv add "redstring[neo4j]"
uv add "redstring[pgvector]"
uv add "redstring[redis]"
uv add "redstring[all]"
One extra covers every model deployment
langchain-openai speaks the OpenAI wire protocol, and so does nearly
everything else: llama.cpp, llama-swap, vLLM, Ollama's compatibility shim, and
OpenAI itself. Pointing at a local server is a base_url, not a different
adapter:
from langchain_openai import ChatOpenAI
from redstring.llm.adapters.langchain import LangChainLlmProvider
chat_model = ChatOpenAI(
model="qwen3-30b",
base_url="http://localhost:8080/v1",
api_key="-", # most local servers ignore it, but the client requires one
)
provider = LangChainLlmProvider(chat_model, model="openai-compatible/qwen3-30b")
The dependency ranges are deliberately wide (langchain-core>=0.3,<2) rather
than pinned. That is what the LlmProvider port is for: a breaking change
upstream touches llm/adapters/langchain.py and nothing else.
Every adapter module imports without its package
The four adapter modules are importable on a base install; only the
constructor that builds its own connection needs the package. So
redstring/__init__.py can export the store types without dragging a
Postgres driver in, and a caller who never touches PgVectorStore.connect
never needs asyncpg.
Reaching one without its extra is an ImportError naming the extra, not a
ModuleNotFoundError naming a package you did not ask for:
PgVectorStore.connect needs asyncpg: install `redstring[pgvector]`
RedisCache.from_url needs redis: install `redstring[redis]`
tests/unit/test_optional_dependency_guards.py asserts both, by putting
None into sys.modules — which is the only way to test it, since everyone
working on this repository has every extra installed and would never see the
failure.
One dependency is core and will stay that way
eventsource-py. redstring.__init__ exports Document,
DocumentExtracted and both projections, and every one of those needs it —
a public API that fails to import without an extra is not a public API. The
events are the write model, not an optional backend.
The <0.11 cap is deliberate: that library is pre-1.0 and its entire API
changed between 0.5 and 0.9.
Verifying the install
import redstring
print(redstring.__version__)
If you are type-checking your own code against this package, py.typed ships
in the wheel, so mypy and pyright see the annotations with nothing configured.
For contributors
A working tree needs the dev tooling and both optional extras — a venv
without them fails collection on the modules that import them rather than
skipping those tests:
uv sync --all-extras
uv run pre-commit install
--all-extras, not --extra dev. This is worth knowing because it has cost
this project two debugging sessions that presented as something else entirely
— a mutation run reporting "0 survivors out of 426" (every mutant killed by an
import error) and 47 phantom mypy errors in untouched files. Note also that
uv add and uv remove re-resolve and can silently narrow the venv back to
dev, so re-sync with --all-extras after any dependency change.
See Contributing.