A simple RAG (Retrieval-Augmented Generation) system for ingesting markdown blog posts into ChromaDB and chatting with your knowledge base using Claude.
This is a companion app this post on Altered Craft
- Ingest markdown files with YAML frontmatter
- Token-based chunking with sliding window overlap
- Interactive CLI chat powered by Claude (Anthropic)
- Switchable storage backend (local or cloud) via environment variables
- ChromaDB's default embedding model (all-MiniLM-L6-v2)
- RSS sync for pulling new articles from alteredcraft.com
Requires Python 3.13+ and uv.
uv sync# 1. Copy the environment template and add your API key
cp dist.env .env
# Edit .env and set ANTHROPIC_API_KEY
# 2. Ingest some markdown files
uv run python ingest.py ./articles
# 3. Start chatting!
uv run python chat.py chatuv run pytestStart an interactive chat session with your knowledge base:
# Basic usage
uv run python chat.py chat
# With options
uv run python chat.py chat \
--collection blog_posts \
--n-results 5 \
--min-relevance 0.5 \
--type deep_diveAsk a single question without entering interactive mode:
uv run python chat.py query "What are the best practices for building AI agents?"# Ingest all .md files from articles directory
uv run python ingest.py ./articles
# Specify a custom collection name
uv run python ingest.py ./articles my_collection
# Ingest without chunking (entire doc = 1 chunk)
uv run python ingest.py ./articles --no-chunkfrom chroma_client import get_chroma_client
client = get_chroma_client()
collection = client.get_collection("blog_posts")
results = collection.query(
query_texts=["How do I build an AI agent?"],
n_results=3
)
for doc, meta in zip(results["documents"][0], results["metadatas"][0]):
print(f"Source: {meta['source_file']}")
print(f"Content: {doc[:200]}...")Copy dist.env to .env and configure:
cp dist.env .env| Variable | Description | Default |
|---|---|---|
ANTHROPIC_API_KEY |
Anthropic API key (required for chat) | - |
ANTHROPIC_MODEL |
Claude model to use | claude-sonnet-4-20250514 |
SYSTEM_PROMPT_FILE |
Path to system prompt file | prompt.txt |
CHAT_LOG_FILE |
Path to debug log file | chat.log |
CHAT_LOG_LEVEL |
Log level (DEBUG, INFO, WARNING, ERROR) | DEBUG |
CHROMA_CLIENT_TYPE |
persistent or cloud |
persistent |
CHROMA_PERSIST_PATH |
Local storage path | ./.chromadb |
CHROMA_TENANT |
Cloud tenant ID | - |
CHROMA_DATABASE |
Cloud database name | - |
CHROMA_API_KEY |
Chroma Cloud API key | - |
export CHROMA_CLIENT_TYPE=cloud
export CHROMA_TENANT=your-tenant-id
export CHROMA_DATABASE=your-database-name
export CHROMA_API_KEY=your-api-key
uv run python ingest.py ./articles.
├── chat.py # Interactive RAG chat CLI
├── chroma_client.py # Client factory (persistent/cloud)
├── ingest.py # Markdown ingestion logic
├── prompt.txt # System prompt for Claude (customizable)
├── scripts/
│ └── sync_articles.py # Sync articles from RSS feed
├── articles/ # Markdown articles
├── tests/ # Unit tests
├── dist.env # Environment template
├── chat.log # Debug log file (auto-created)
├── .chromadb/ # Local database (auto-created)
└── pyproject.toml
Articles use YAML frontmatter:
---
author: Sam Keen
publish_date: January 15, 2024
title: My Article
type: weekly_review
url: https://alteredcraft.com/p/my-article
---
# My Article
Introduction...
## Section One
Content...The ingester extracts frontmatter as metadata and chunks content using a sliding window tokenizer (256 tokens max, 50 token overlap).
