pydantic-rag-agent
A retrieval-augmented generation (RAG) agent built on Pydantic and managed with uv.
Status: feature-complete for a local, single-user deployment. M0–M5 are done and M6 is closed apart from OPS-1 (instrumentation), which is deliberately out of scope. Ingestion, retrieval, the agent, the API, the CLI and the Streamlit UI all work; the suite runs offline at 92% branch coverage.
mainis the state described indocs/overview.md; the ordered plan behind it is indocs/features/anddocs/roadmap.md.
Why uv?
Dependency resolution and virtualenv handling live in one fast tool, and uv.lock
pins the exact graph so your CI and your laptop agree on what "it works" means.
Documentation
Full documentation lives in docs/ — start with the
documentation index or the project overview.
Design rationale is recorded as decision records.
Coding agents should read AGENTS.md first.
Requirements
- uv 0.13 or newer
- Python 3.11+ — uv will fetch and manage the interpreter for you if needed
Install
uv add "pydantic-rag-agent" # core: agent, loaders, CLI, in-memory store
uv add "pydantic-rag-agent[chroma]" # plus the default persistent store
uv add "pydantic-rag-agent[chroma,ui]" # plus the Streamlit UI
uv add "pydantic-rag-agent[chroma,api]" # plus the FastAPI integration
uv add "pydantic-rag-agent[chroma,s3]" # plus originals in a bucket
uv add "pydantic-rag-agent[all]" # everything
The core install has no vector database: it runs with the in-memory store, which is fine for a script that ingests and asks in one go. Anything the agent does not need at import time is an extra, and a missing one tells you which to install.
Published as 0.1.1 — see the changelog for what is in it. Before 1.0 the public API may change in a minor release, with a migration note.
Then let the wizard write your .env — provider, models, where documents live, which
vector store — so the first run is configuration rather than reading a reference table:
pydantic-rag-agent configure
As a library, from the repository today (and from PyPI once published):
from pydantic_rag_agent import RagAgent
agent = RagAgent.from_env()
agent.ingest_sync("./docs")
print(agent.ask_sync("How long are backups retained?").text)
The async methods (await agent.ask(...), async for update in agent.stream(...)) are
what a web app wants; the sync helpers exist so a script does not have to become async to
ask one question. Answers carry citations back to the source document, including the
locator of the stored original (file:// or s3://).
Already have a FastAPI app? Mount the router instead of running a second service:
from contextlib import asynccontextmanager
from fastapi import FastAPI
from pydantic_rag_agent import RagAgent
from pydantic_rag_agent.integrations.fastapi import create_router
agent = RagAgent.from_env()
@asynccontextmanager
async def lifespan(app: FastAPI):
await agent.check_index()
try:
yield
finally:
await agent.aclose()
app = FastAPI(lifespan=lifespan)
app.include_router(create_router(agent), prefix="/knowledge")
The router brings the routes; your app keeps its lifespan, its auth and its shutdown.
Any OpenAI-speaking chat UI can be pointed at the agent instead of a bespoke frontend:
from pydantic_rag_agent.integrations.fastapi import create_openai_router
app.include_router(create_openai_router(agent, model_id="acme-knowledge"), prefix="/openai")
Point the client at https://your-host/openai/v1. Requests are stateless (the client
sends the thread), caller-supplied system messages are ignored, and answers carry a
Sources: footer. serve already exposes the same route at /v1, so a chat UI can be
pointed at a running server without writing any code — --openai-model-id chooses the name
the client shows. See ADR 0014.
Use your own model provider
OpenRouter is the default, not a requirement. Chat is built against any OpenAI-compatible endpoint, so the provider is configuration:
PRAGENT_PROVIDER__BASE_URL=https://api.openai.com/v1
PRAGENT_PROVIDER__MODEL=gpt-4.1-mini
PRAGENT_PROVIDER__API_KEY=sk-…
The model id is bare — no openai: or openrouter: prefix — and the /v1 belongs in the
base URL.
| Endpoint | BASE_URL |
Worth knowing |
|---|---|---|
| OpenAI | https://api.openai.com/v1 |
The example above |
| Ollama | http://localhost:11434/v1 |
Any non-empty key, e.g. ollama; use a model with tool support |
| vLLM | http://localhost:8000/v1 |
Start it with auto tool choice and a tool-call parser |
| LM Studio | http://localhost:1234/v1 |
Any non-empty key |
| Groq | https://api.groq.com/openai/v1 |
Provider key; tool support varies by model |
Three things that apply to every provider:
- Embeddings are a separate node. Chat and embeddings can come from different places;
point
PRAGENT_EMBEDDINGS__BASE_URL/__MODEL/__DIMENSIONSat a provider with an OpenAI-compatible embeddings endpoint if your chat provider has none. SetPRAGENT_EMBEDDINGS__API_KEYexplicitly when it differs — otherwise it falls back to the provider key. - Tool calling is required. The agent retrieves by calling
search_knowledge_base. A model or server that ignorestoolswill answer from general knowledge instead of your documents; a question whose answer is only in the corpus is the quickest test. - Changing the embedding model or width means reindexing. The width is written into the
index metadata and checked at startup, so the guard refuses to mix vectors from different
models.
DIMENSIONSmust also match what the model actually emits — 768 fornomic-embed-text, 1024 is a valid Matryoshka width fortext-embedding-3-small.
Images are captioned by PRAGENT_PROVIDER__CAPTION_MODEL (default: the chat model). If you
ingest images and the chat model has no vision, set a vision-capable caption model.
Every variable, its default and the programmatic equivalent are in
docs/reference/configuration.md.
Getting started from the repository
# Clone and enter the project
git clone git@github.com:hanku4u/pydantic-rag-agent.git
cd pydantic-rag-agent
# Create .venv and install the project plus its dependencies
uv sync
# Provide the one credential: chat, captions, and embeddings all use it
cp .env.example .env
# then edit .env and set OPENROUTER_API_KEY
# Index and ask — the agent runs in this process, no server needed
uv run pydantic-rag-agent ingest ./docs
uv run pydantic-rag-agent ask "How long are backups retained?"
# Optional: the API and web UI together
uv run pydantic-rag-agent dev
The terminal answer streams and ends with a table of the sources it cited. The chat UI
from dev is at http://127.0.0.1:8501. --api-url makes any command talk to a running
server instead of building the agent locally.
Day-to-day commands
| Task | Command |
|---|---|
| Install or refresh dependencies | uv sync |
| Add a runtime dependency | uv add <package> |
| Add a dev-only dependency | uv add --dev <package> |
| Remove a dependency | uv remove <package> |
| Run a command inside the venv | uv run <command> |
| Run a script without installing it | uv run path/to/script.py |
Project layout
pydantic-rag-agent/
├── pyproject.toml # project metadata, dependencies, CLI entry point
├── uv.lock # exact resolved versions — commit this
├── .python-version # interpreter uv should use
├── AGENTS.md # orientation for coding agents
├── docs/ # design, backlog, decisions — start at docs/README.md
├── src/
│ └── pydantic_rag_agent/
│ ├── __init__.py # package marker, no import-time side effects
│ └── cli.py # command-line entry point (`main`)
└── README.md
The src/ layout keeps the package importable from an installed build rather
than by accident from the working directory, which is what makes
uv run pytest behave the same as a real install.
Configuration
Settings are a tree of six nodes — provider, embeddings, vector store, storage,
ingestion, agent — read from PRAGENT_* environment variables via pydantic-settings.
Nesting is addressed with a double underscore: PRAGENT_PROVIDER__MODEL,
PRAGENT_AGENT__TOP_K. Each node can also be constructed in code, which is how the
library is used.
Credentials keep their conventional names (OPENROUTER_API_KEY) and are never read from
a committed file. Every variable, its default, and the mapping from the pre-0.1.0 flat
names are in docs/reference/configuration.md.
The .gitignore ignores .env and any .env.* file except .env.example.
Contributing
uv syncto get a working environment.- Pick a feature ID from the backlog and reference it in your commit subject.
- Add tests under
tests/next to the module they cover. uv run pytestbefore opening a pull request.
License
MIT. Use it, fork it, ship it — just keep the copyright notice.
The dependency stack is deliberately permissive end to end; the one AGPL package that
previously blocked this was replaced. See
docs/reference/dependencies.md for the details, and
ADR 0007 for the reasoning.
Metadata
Release files for pydantic-rag-agent 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pydantic_rag_agent-0.1.1.tar.gz | 90.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pydantic_rag_agent-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 203.4 kB
Release files / pydantic_rag_agent-0.1.1.tar.gz
| Download URL | pydantic_rag_agent-0.1.1.tar.gz |
|---|---|
| Size | 90.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bd5a3963d36bb315972804b195631649e6e027240ee62050377d59ba27c100be
|
|
BLAKE2b-256 checksum How to use checksums |
8e9be176ed5301bc715d928231240d4dce20de1a25c549bd97d3484d973b50d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / pydantic_rag_agent-0.1.1-py3-none-any.whl
| Download URL | pydantic_rag_agent-0.1.1-py3-none-any.whl |
|---|---|
| Size | 112.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9b3e3c134f5533e8e76ddef1f7012712aeb0db6b67b405eb8c35c32a33bad701
|
|
BLAKE2b-256 checksum How to use checksums |
c2a5d35c36b91c6d63d2f06f4793101020ca31689d0f4225592e9f23558eaf30
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for pydantic-rag-agent 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.