← Blog

How to Build an AI Research Assistant with Claude

2026-09-27 · 5 min read · SubToAPI Team

Building an AI research assistant with Claude means combining three things: Claude's reasoning and summarization ability, tool use for pulling in real information (search, documents, databases), and a persistence layer that keeps track of what's been gathered across a session. This article walks through the architecture and gives you working code for each piece, from a single-turn query answerer to a multi-step assistant that plans, searches, and synthesizes.

If you just want the short answer: you send Claude a system prompt that defines its role as a researcher, give it tool definitions for search/fetch/read operations, let it call those tools in a loop until it has enough information, then ask it to produce a structured summary with citations. The rest of this guide fills in the details.

What a research assistant actually needs

A "research assistant" is not just a chatbot with a longer prompt. It needs:

Claude handles the reasoning and synthesis well out of the box. The tool orchestration and memory are on you to build, but the pattern is straightforward once you've done it once.

Step 1: Define the system prompt

Keep the system prompt focused on behavior, not personality. Tell Claude what to do when it doesn't have enough information, and how to format citations.

You are a research assistant. When asked a question:
1. Break it into sub-questions if needed.
2. Use the search_web and fetch_page tools to gather information.
3. Do not answer from memory alone if the question is time-sensitive or factual.
4. Cite sources inline using [1], [2], etc., and list them at the end.
5. If sources conflict, say so explicitly.

Step 2: Give Claude tools

Claude's tool use (function calling) lets you define a schema Claude can call. A minimal research toolset looks like this:

[
  {
    "name": "search_web",
    "description": "Search the web for a query and return top results with snippets.",
    "input_schema": {
      "type": "object",
      "properties": {
        "query": { "type": "string" }
      },
      "required": ["query"]
    }
  },
  {
    "name": "fetch_page",
    "description": "Fetch and extract readable text content from a URL.",
    "input_schema": {
      "type": "object",
      "properties": {
        "url": { "type": "string" }
      },
      "required": ["url"]
    }
  }
]

You implement search_web and fetch_page yourself (a search API plus an HTML-to-text extractor), and hand results back to Claude in a tool_result block. Claude decides when to call them and when it has enough to answer. Read through /docs/tools if you're setting this up for the first time — the request/response shape for multi-step tool loops is the part people get wrong most often.

Step 3: Run the agent loop

The core loop is: send messages, check if Claude wants to call a tool, execute it, send the result back, repeat until Claude returns a final text answer.

async function runResearchLoop(question) {
  let messages = [{ role: "user", content: question }];

  while (true) {
    const res = 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-5",
        max_tokens: 2048,
        system: SYSTEM_PROMPT,
        tools: TOOLS,
        messages
      })
    });

    const data = await res.json();
    messages.push({ role: "assistant", content: data.content });

    const toolCalls = data.content.filter(b => b.type === "tool_use");
    if (toolCalls.length === 0) {
      return data.content.find(b => b.type === "text")?.text;
    }

    const toolResults = [];
    for (const call of toolCalls) {
      const result = await executeTool(call.name, call.input);
      toolResults.push({
        type: "tool_result",
        tool_use_id: call.id,
        content: JSON.stringify(result)
      });
    }
    messages.push({ role: "user", content: toolResults });
  }
}

This loop is model-agnostic in structure — it's the same pattern whether you're hitting Claude directly or a wrapper. If you're routing through SubToAPI, you get this endpoint behind your own sub_live_... key, with streaming and usage metadata included, so you can track token spend per research session without extra instrumentation. See /docs/messages for the full request/response reference and /docs/streaming if you want partial results rendered as Claude reasons.

Step 4: Add memory for multi-session research

For anything beyond a single query, store the conversation and any gathered sources outside the request itself — Claude's context window resets per API call unless you resend history. A simple approach:

This is deliberately simple. Don't reach for a vector database until you actually need semantic search over dozens of past sessions — most research assistants for a single user or small team work fine with plain relational storage and full-text search.

Step 5: Structure the output

Ask Claude to return findings in a predictable shape so your frontend can render citations, not just a paragraph of text:

{
  "summary": "...",
  "findings": [
    { "claim": "...", "sources": [1, 2] }
  ],
  "sources": [
    { "id": 1, "title": "...", "url": "..." }
  ]
}

Add "Return your answer as JSON matching this schema" to the prompt and validate the response before showing it to users.

Getting from prototype to production

The loop above works in a script. Making it reliable for real users means handling rate limits, retries, streaming partial output, and giving each user or team their own scoped API key so you can track usage and costs per project. SubToAPI wraps your existing Claude access into a standard HTTPS API with per-key usage metadata, team seats, and streaming built in, which is useful once your research assistant has more than one user hitting it. Plans start at €9/month with a free trial — check /pricing and get started at /signup, or skim /docs/quickstart for the fastest path to a working key.

questions

Do I need a vector database to build a research assistant with Claude? No, not initially. Claude's context window and tool use handle most single-session research. Add a vector store only when you need semantic search across large volumes of past research or documents.

How does Claude decide when to stop searching and answer? It's driven by your system prompt and the tool results it receives. If you instruct it to keep gathering sources until it has enough to answer confidently, and give it a way to judge "enough" (e.g., corroborating sources), it will stop calling tools and return text.

Can I use Claude's research assistant pattern with streaming responses? Yes. Stream the final synthesis step so users see the answer as it's generated, while tool-calling steps happen server-side without streaming. See /docs/streaming for the event format.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →