RAG with Claude in 30 lines
Web-grounded Q&A using SiftQ retrieval + Claude Sonnet, with inline citations.
Prerequisites
- $
pip install anthropic requests - $
export SIFTQ_API_KEY=mk-... - $
export ANTHROPIC_API_KEY=sk-ant-...
How it works
The canonical RAG loop. Pull ranked passages from SiftQ, paste them into Claude's context window with strict instructions to cite numerically, and surface a verifiable answer. This pattern is the entire reason most products move from raw LLM calls to retrieval-augmented generation: the model stops hallucinating because every claim has to point at a source URL.
The recipe
import os
import requests
import anthropic
claude = anthropic.Anthropic()
SIFTQ_KEY = os.environ["SIFTQ_API_KEY"]
def search(q: str, n: int = 8) -> list[dict]:
r = requests.post(
"https://api.siftq.com/v1/search",
headers={"Authorization": f"Bearer {SIFTQ_KEY}"},
json={"q": q, "scope": "webpage", "size": str(n), "conciseSnippet": True},
timeout=20,
)
r.raise_for_status()
return r.json().get("webpages", [])
def answer(question: str) -> tuple[str, list[str]]:
"""Returns (answer text with [N] citations, list of source URLs)."""
hits = search(question)
if not hits:
return "No sources found.", []
sources = "\n\n".join(
f"[{i + 1}] {h['title']}\n{h['snippet']}\nURL: {h['link']}"
for i, h in enumerate(hits)
)
msg = claude.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=(
"Answer using only the sources provided. "
"Cite as [N] inline next to every factual claim. "
"If the sources do not contain the answer, say so."
),
messages=[{
"role": "user",
"content": f"SOURCES:\n{sources}\n\nQUESTION: {question}",
}],
)
return msg.content[0].text, [h["link"] for h in hits]
if __name__ == "__main__":
text, urls = answer("What did Anthropic ship in the past month?")
print(text)
print("\nSources:")
for i, u in enumerate(urls, start=1):
print(f" [{i}] {u}")
Variations
Call SiftQ twice — once with scope=webpage, once with scope=scholar — and concatenate. Useful for technical questions that span news + academic.
Pass SiftQ's top 20 through Cohere Rerank or Jina Reranker before sending to Claude. Costs more, lifts precision 5-15% on hard queries.
Replace messages.create with messages.stream and yield tokens as they arrive. Sub-second first-token under SiftQ + Claude Sonnet Fast.