Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Distill Documentation

## Guides

- [Getting Started](guides/getting-started.md) — Install, configure, and run your first dedup
- [Memory](guides/memory.md) — Persistent context memory across sessions
- [Sessions](guides/sessions.md) — Token-budgeted context windows
- [MCP Integration](guides/mcp.md) — Use Distill with Claude Desktop, Cursor, and other MCP clients
- [Deployment](guides/deployment.md) — Docker, binary, and cloud deployment

## Reference

- [API Reference](reference/api.md) — All REST endpoints
- [Configuration](reference/configuration.md) — Config file, environment variables, CLI flags
- [OpenAPI Spec](../openapi.yaml) — Machine-readable API specification

## Examples

- [LangChain Integration](examples/langchain.md)
- [RAG Pipeline](examples/rag-pipeline.md)
98 changes: 98 additions & 0 deletions docs/examples/langchain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# LangChain Integration

Use Distill as a persistent memory layer for LangChain agents.

## Setup

```bash
pip install langchain requests
```

## Memory wrapper

```python
import requests
from langchain.memory import BaseMemory

class DistillMemory(BaseMemory):
"""LangChain memory backed by Distill's memory API."""

base_url: str = "http://localhost:8080"
api_key: str | None = None
agent_id: str = "langchain-agent"
memory_key: str = "history"

@property
def memory_variables(self) -> list[str]:
return [self.memory_key]

def _headers(self) -> dict:
h = {"Content-Type": "application/json"}
if self.api_key:
h["Authorization"] = f"Bearer {self.api_key}"
return h

def load_memory_variables(self, inputs: dict) -> dict:
query = inputs.get("input", "")
resp = requests.post(
f"{self.base_url}/v1/memory/recall",
headers=self._headers(),
json={
"query": query,
"agent_id": self.agent_id,
"top_k": 5,
},
)
resp.raise_for_status()
memories = resp.json().get("memories", [])
text = "\n".join(m["content"] for m in memories)
return {self.memory_key: text}

def save_context(self, inputs: dict, outputs: dict) -> None:
content = f"User: {inputs.get('input', '')}\nAssistant: {outputs.get('output', '')}"
requests.post(
f"{self.base_url}/v1/memory/store",
headers=self._headers(),
json={
"content": content,
"agent_id": self.agent_id,
"tags": ["conversation"],
"auto_classify": True,
},
).raise_for_status()

def clear(self) -> None:
requests.post(
f"{self.base_url}/v1/memory/forget",
headers=self._headers(),
json={"agent_id": self.agent_id},
).raise_for_status()
```

## Usage with an agent

```python
from langchain.chat_models import ChatOpenAI
from langchain.chains import ConversationChain

memory = DistillMemory(
base_url="http://localhost:8080",
agent_id="my-agent",
)

chain = ConversationChain(
llm=ChatOpenAI(model="gpt-4o"),
memory=memory,
)

# Memories persist across restarts
response = chain.predict(input="What did we discuss yesterday?")
```

## Why use Distill instead of LangChain's built-in memory?

- **Persistence** — survives process restarts, stored in SQLite
- **Deduplication** — repeated context is stored once
- **Sensitivity tagging** — PII and credentials are flagged automatically
- **Decay** — old memories lose relevance over time
- **Conflict detection** — contradictory memories are surfaced
150 changes: 150 additions & 0 deletions docs/examples/rag-pipeline.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# RAG Pipeline with Distill

Use Distill's dedup + memory pipeline to build a retrieval-augmented generation system that avoids redundant context and remembers past interactions.

## Architecture

```
Documents → Distill (dedup + embed) → Memory Store
User Query → Distill (recall) → Ranked Context → LLM → Response
Distill (store answer)
```

## Ingest documents

Deduplicate and store document chunks:

```python
import requests

DISTILL = "http://localhost:8080"

def ingest(chunks: list[str], source: str):
"""Dedup chunks, then store unique ones as memories."""
# Deduplicate
resp = requests.post(f"{DISTILL}/v1/dedupe", json={
"chunks": chunks,
})
resp.raise_for_status()
unique = resp.json()["unique_chunks"]

# Store each unique chunk
for chunk in unique:
requests.post(f"{DISTILL}/v1/memory/store", json={
"content": chunk,
"agent_id": "rag-pipeline",
"tags": ["document", source],
"auto_classify": True,
}).raise_for_status()

print(f"Stored {len(unique)}/{len(chunks)} chunks from {source}")
```

## Query with context

```python
def query(question: str, llm_fn) -> str:
"""Recall relevant context and generate an answer."""
# Recall from memory
resp = requests.post(f"{DISTILL}/v1/memory/recall", json={
"query": question,
"agent_id": "rag-pipeline",
"top_k": 10,
"min_relevance": 0.3,
"boost_tags": ["document"],
})
resp.raise_for_status()
memories = resp.json()["memories"]

# Build prompt
context = "\n---\n".join(m["content"] for m in memories)
prompt = f"Context:\n{context}\n\nQuestion: {question}\nAnswer:"

answer = llm_fn(prompt)

# Store the Q&A pair for future recall
requests.post(f"{DISTILL}/v1/memory/store", json={
"content": f"Q: {question}\nA: {answer}",
"agent_id": "rag-pipeline",
"tags": ["qa"],
}).raise_for_status()

return answer
```

## Handle stale knowledge

When documents are updated, supersede old memories:

```python
def update_document(old_memory_id: str, new_content: str):
"""Replace outdated memory with new version."""
# Store new version
resp = requests.post(f"{DISTILL}/v1/memory/store", json={
"content": new_content,
"agent_id": "rag-pipeline",
"tags": ["document"],
})
resp.raise_for_status()
new_id = resp.json()["id"]

# Supersede old version
requests.post(f"{DISTILL}/v1/memory/supersede", json={
"id": old_memory_id,
"new_id": new_id,
}).raise_for_status()
```

## Session-based context window

For multi-turn conversations, use sessions to manage token budgets:

```python
def create_session():
resp = requests.post(f"{DISTILL}/v1/session/create", json={
"max_tokens": 4000,
"strategy": "sliding_window",
})
return resp.json()["session_id"]

def add_to_session(session_id: str, role: str, content: str):
requests.post(f"{DISTILL}/v1/session/push", json={
"session_id": session_id,
"entries": [{"role": role, "content": content}],
}).raise_for_status()

def get_context(session_id: str) -> list[dict]:
resp = requests.post(f"{DISTILL}/v1/session/context", json={
"session_id": session_id,
})
return resp.json()["entries"]
```

## Full example

```python
from openai import OpenAI

client = OpenAI()

def llm(prompt: str) -> str:
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content

# Ingest
chunks = [
"Distill deduplicates context before sending to LLMs.",
"Memory entries decay over time based on access patterns.",
"Sensitivity classification detects PII automatically.",
]
ingest(chunks, source="distill-docs")

# Query
answer = query("How does Distill handle sensitive data?", llm)
print(answer)
```
91 changes: 91 additions & 0 deletions docs/guides/deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Deployment

## Docker

```bash
docker run -p 8080:8080 \
-e OPENAI_API_KEY=sk-... \
ghcr.io/siddhant-k-code/distill:latest \
api --memory --session
```

With a persistent volume for memory:

```bash
docker run -p 8080:8080 \
-v distill-data:/data \
-e OPENAI_API_KEY=sk-... \
ghcr.io/siddhant-k-code/distill:latest \
api --memory --memory-db /data/memory.db --session --session-db /data/sessions.db
```

## Docker Compose

```yaml
version: "3.8"
services:
distill:
image: ghcr.io/siddhant-k-code/distill:latest
ports:
- "8080:8080"
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
command: api --memory --session
volumes:
- distill-data:/data

volumes:
distill-data:
```

## Binary

Download from [GitHub Releases](https://github.com/Siddhant-K-code/distill/releases) and run directly:

```bash
distill api --memory --session
```

## Fly.io

A `fly.toml` is included in the repository:

```bash
fly launch
fly secrets set OPENAI_API_KEY=sk-...
fly deploy
```

## Render

A `render.yaml` is included for one-click deployment to Render.

## Environment variables

| Variable | Description |
|----------|-------------|
| `OPENAI_API_KEY` | OpenAI API key for embeddings |
| `COHERE_API_KEY` | Cohere API key (when using `--embedding-provider cohere`) |
| `DISTILL_API_KEYS` | Comma-separated API keys for authentication |

## Observability

### Prometheus metrics

Available at `/metrics`:

```bash
curl localhost:8080/metrics
```

### OpenTelemetry tracing

```bash
distill api --otel-endpoint localhost:4317
# or
distill api --otel-stdout # print traces to stdout
```

### Grafana

Import the dashboard template from `grafana/dashboard.json`.
Loading
Loading