Build AI-Powered Search with Claude: A Developer Guide
AI-powered search with Claude means using retrieval to find relevant content and Claude to understand the query, reason over the results, and generate a grounded, natural-language answer — instead of returning a list of blue links. It's the architecture behind "ask your docs a question" products, internal knowledge bases, and support search boxes that actually answer questions.
Claude itself is not a search engine. It has no live index of your data and, unless you give it tools, no way to look anything up. What it's excellent at is turning a pile of retrieved text into a coherent, cited answer, deciding when it needs more information, and calling a search function on its own when the first query didn't return enough. This article covers the architecture, the retrieval step, and how to wire Claude into a working search pipeline using tool use and streaming.
The two-layer architecture
Every working "AI search" system has two layers:
- Retrieval layer — finds candidate documents or passages. This is usually a vector database (pgvector, Pinecone, Weaviate, Qdrant) storing embeddings of your content, or a traditional keyword/BM25 index, or both combined (hybrid search).
- Reasoning layer — Claude. It takes the query plus retrieved passages and produces an answer, decides if results are insufficient and re-queries, and cites sources.
Skipping the retrieval layer and just asking Claude "what does our documentation say about X" only works if the docs fit in the context window and you paste them in directly. For anything beyond a handful of pages, you need retrieval.
Step 1: Index your content
Chunk your documents into passages of a few hundred tokens each, generate embeddings for each chunk, and store them with metadata (source URL, title, timestamp). Chunking strategy matters more than model choice here — split on natural boundaries (headings, paragraphs) rather than fixed character counts, and keep enough overlap that answers don't get cut off mid-sentence.
// Pseudocode: index a document
const chunks = splitIntoChunks(document.text, { maxTokens: 400, overlap: 50 });
for (const chunk of chunks) {
const embedding = await embed(chunk.text);
await vectorDB.upsert({
id: chunk.id,
vector: embedding,
metadata: { source: document.url, title: document.title, text: chunk.text }
});
}
Step 2: Retrieve, then ask Claude
At query time, embed the user's question, pull the top-k matching chunks, and pass them to Claude as context. Ask Claude to answer only from the provided passages and to cite which source each claim came from — this cuts down on hallucinated answers and makes the search feel trustworthy.
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 600,
"system": "Answer using only the provided passages. Cite the source title after each claim in brackets. If the passages do not answer the question, say so.",
"messages": [{
"role": "user",
"content": "Passages:\n1. [Refund Policy] Refunds are processed within 5 business days.\n2. [Billing FAQ] Refund requests go to billing@example.com.\n\nQuestion: How long do refunds take?"
}]
}'
This request/response pattern is the core of most retrieval-augmented search products. For the full request shape, see the messages docs.
Step 3: Let Claude drive the search itself
A more capable pattern is to give Claude a search tool and let it decide when and what to query, rather than always retrieving once before every call. This handles multi-hop questions ("compare our refund policy to last year's") where a single retrieval pass isn't enough.
const tools = [{
name: "search_docs",
description: "Search the knowledge base for relevant passages",
input_schema: {
type: "object",
properties: { query: { type: "string" } },
required: ["query"]
}
}];
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"content-type": "application/json"
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 800,
tools,
messages: [{ role: "user", content: "How does our refund policy compare to 2023?" }]
})
});
When Claude responds with a tool_use block, run your retrieval function with the query it generated, feed the results back as a tool_result, and let Claude continue the conversation. It might call the tool two or three times before answering. This loop is described in detail in the tool use docs.
Step 4: Stream the answer
Search UIs feel much faster when the answer streams token by token instead of appearing all at once after a multi-second wait, especially once you've added a retrieval round-trip and possibly a tool-use loop. Set "stream": true and read the server-sent events as they arrive. See the streaming guide for the event format and a working example.
Production considerations
A few things separate a search demo from something you'd put in front of real users:
- Query rewriting. Raw user queries are often too short or too colloquial for good vector search. Have Claude rewrite the query into a search-optimized form before retrieval.
- Reranking. Vector search returns approximate matches; a cheap reranking pass (or asking Claude to pick the most relevant 3 of 10 retrieved chunks) improves answer quality noticeably.
- No-answer handling. Explicitly instruct Claude to say "not found in the knowledge base" rather than guessing — this is the single biggest lever against hallucinated search results.
- Cost and usage tracking. Search traffic can spike unpredictably, and every query potentially triggers multiple Claude calls (rewrite, tool-use loop, final answer). If you're building this as a product feature rather than an internal tool, you'll want per-customer usage visibility and rate limits rather than a single shared key.
This is where SubToAPI fits in: it turns your existing Claude access into an HTTPS API with application-scoped keys (sub_live_...), streaming, tool use, and usage metadata per key, so you can give each customer or environment its own key and see exactly what their search queries cost. Start with the quickstart, check pricing, or sign up for a free trial.
questions
Do I need a vector database to build search with Claude? For anything beyond a small, static set of documents, yes — a vector database (or hybrid keyword + vector index) handles retrieval, and Claude handles reasoning over the results. Pasting your entire knowledge base into every prompt doesn't scale in cost or latency.
Can Claude search the web directly? Not on its own. Claude can call a search tool you provide (a web search API, your internal index, etc.) via tool use, decide what to query, and reason over the results, but it needs that tool wired up — it has no built-in live index.
How do I stop Claude from making up answers in search results? Restrict it explicitly to the retrieved passages, require citations for each claim, and instruct it to say when the passages don't answer the question. Reranking retrieved chunks before passing them to Claude also reduces irrelevant context that leads to guessing.