Build a research agent that cites its sources in Python
A research agent needs several searches from different angles, a way to merge and rank what comes back, and evidence it can cite. ContextFetch's /v1/research endpoint does all three in one call and returns an evidence pack of verbatim passages numbered [1], [2] and so on. This guide builds the agent in plain Python, adds a quoted direct answer from /v1/answer, and shows where your LLM fits in.
1. Get an API key
Claim a free key with one POST request. It needs no signup and comes with 2,500 credits, and one free key is issued per network. Export it so the code below can read it.
You can also try the API with no key at all: requests without an Authorization header get 25 free calls per IP address per UTC day.
curl -X POST https://api.contextfetch.dev/v1/keys/free# {"apiKey": "cf_live_...", "credits": 2500}export CONTEXTFETCH_API_KEY="cf_live_..."2. Plan the searches
One search rarely covers a question. Ask how vector databases handle filtering and the useful pages split into explainers, documentation and benchmarks. /v1/research takes up to 5 subQueries and runs them concurrently through the search pipeline. If you leave them out, it derives them from your question: the question itself, the question plus "documentation", and the question plus "benchmark" or "example".
Writing your own sub-queries is worth it when you know which angles matter. Each sub-query costs one credit, and numResultsPerQuery sets how many results each one contributes, from 1 to 10.
research = post("/v1/research", { "query": question, "subQueries": [ "vector database metadata filtering", "pre-filtering vs post-filtering vector search", "filtered HNSW search performance", ], "numResultsPerQuery": 4,})3. Collect ranked evidence
The response deduplicates pages by URL across all sub-queries and ranks every page against your original question with a cross-encoder. Each item in results has the url, title, score, highlights and prunedContext, plus subQueries: the searches that found it. A page that several searches found is usually a strong source.
The evidence field is the same material as one Markdown document: numbered sources with their passages quoted verbatim, ready to paste into a prompt. Nothing in it is generated, so every sentence can be traced back to its URL.
4. Get a direct answer with citations
For a short factual question, /v1/answer searches and returns up to 3 of the most relevant passages from the top pages, each followed by [n], with citations listing the URLs. It is extractive: the passages are quoted, not written by a model. That makes it a useful fact-check step and a cheap answer for simple questions, at one credit per call.
5. Hand the evidence to your LLM
ContextFetch runs no generative model, so the final write-up is your model's job. Put the evidence pack in the prompt and tell the model to cite sources by their numbers. Because the pack is already pruned to relevant passages the prompt stays small, and because the passages are verbatim you can check every citation the model makes.
- Ask the model to answer only from the evidence.
- Ask it to cite with the [n] numbers from the pack.
- Keep the URLs from
resultsso you can show them next to the answer.
prompt = f"""Answer the question using only the evidence below.Cite sources as [n]. If the evidence does not answer it, say so. Question: {question} {research["evidence"]}"""The complete code
Python, runnable as is with CONTEXTFETCH_API_KEY set. We ran it against the live API; the output below is what it printed.
import jsonimport osimport urllib.request API = "https://api.contextfetch.dev"KEY = os.environ["CONTEXTFETCH_API_KEY"] def post(path, body): req = urllib.request.Request( API + path, data=json.dumps(body).encode(), headers={"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}, ) with urllib.request.urlopen(req, timeout=90) as resp: return json.load(resp) question = "how do vector databases handle metadata filtering"research = post("/v1/research", { "query": question, "subQueries": [ "vector database metadata filtering", "pre-filtering vs post-filtering vector search", "filtered HNSW search performance", ], "numResultsPerQuery": 4,}) for sq in research["subQueries"]: print(f"searched: {sq['query']} ({sq['status']}, {sq['results']} results)") print("\nTop sources:")for r in research["results"][:5]: print(f"- {r['score']:.2f} {r['title']}\n {r['url']}") print("\nEvidence pack, first 500 characters:")print(research["evidence"][:500]) answer = post("/v1/answer", {"query": "what is pre-filtering in vector search"})print("\nQuoted answer:")print(answer["answer"][:500])for n, c in enumerate(answer["citations"], 1): print(f"[{n}] {c['url']}") spent = research["costDollars"]["total"] + answer["costDollars"]["total"]print(f"\ncost: ${spent:.3f}") searched: vector database metadata filtering (ok, 4 results)
searched: pre-filtering vs post-filtering vector search (ok, 4 results)
searched: filtered HNSW search performance (ok, 4 results)
Top sources:
- 1.00 Metadata Filtering in Vector Search, Explained | AI/TLDR
https://ai-tldr.dev/learn/embeddings-vector-databases/vectors-in-production/vector-metadata-filtering
- 1.00 Pre and Post Filtering in Vector Search with Metadata and RAG Pipelines
https://dev.to/volland/pre-and-post-filtering-in-vector-search-with-metadata-and-rag-pipelines-2hji
- 1.00 Metadata Indexing in Vector Databases
https://teachyou.ai/blog/vector-db-metadata-indexing
- 1.00 Accurate and Efficient Metadata Filtering in Pinecone Vector Database
https://www.pinecone.io/research/accurate-and-efficient-metadata-filtering-in-pinecones-serverless-vector-database
- 1.00 Pre-filtering vs. Post-filtering — About Vector Database
https://aboutvectordatabase.com/learn/pre-filtering-vs-post-filtering
Evidence pack, first 500 characters:
# Evidence pack: how do vector databases handle metadata filtering
Passages quoted verbatim from the sources (nothing generated). Cite them as [n].
## [1] Metadata Filtering in Vector Search, Explained | AI/TLDR
https://ai-tldr.dev/learn/embeddings-vector-databases/vectors-in-production/vector-metadata-filtering · published 2026-06-12 · score 1.000 · found by "vector database metadata filtering"
**Adding a filter often slows the search down or degrades its accuracy**, because the filter disr
Quoted answer:
Pre-filtering is a more intricate approach but can be more effective in some instances. In pre-filtering, metadata is used as the primary filter before vector search takes place. This means that only data that meets the metadata criteria is passed into the vector search process, limiting the scope o…
[1] https://dev.to/volland/pre-and-post-filtering-in-vector-search-with-metadata-and-rag-pipelines-2hji
[2] https://theaidatabaseblog.com/learn/pre-filtering-vs-post-filtering-vs-in-graph-filtering
cost: $0.004The same call with curl
The raw HTTP request, and the response it returned (long text fields and lists shortened).
curl -s https://api.contextfetch.dev/v1/research \ -H "Authorization: Bearer $CONTEXTFETCH_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query": "how do vector databases handle metadata filtering", "numResultsPerQuery": 3}'{
"query": "how do vector databases handle metadata filtering",
"subQueries": [
{
"query": "how do vector databases handle metadata filtering",
"status": "ok",
"results": 3
},
{
"query": "how do vector databases handle metadata filtering documentation",
"status": "ok",
"results": 3
},
{
"query": "how do vector databases handle metadata filtering example",
"status": "ok",
"results": 3
}
],
"evidence": "# Evidence pack: how do vector databases handle metadata filtering\n\nPassages quoted verbatim from the sources (nothing generated). Cite them as [n].\n\n## [1] Pre and Post Filtering in Vector Search with Metadata and RAG Pipelines\n\nhttps://dev.to/volland/pre-and-post-filtering-in-v…",
"requestId": "140a6f562c784bf3aa4021368cd49b9f",
"results": [
{
"id": "002e918e-e0db-5d25-ad2b-505e9b7670f9",
"url": "https://dev.to/volland/pre-and-post-filtering-in-vector-search-with-metadata-and-rag-pipelines-2hji",
"title": "Pre and Post Filtering in Vector Search with Metadata and RAG Pipelines",
"score": 0.9996404845034781,
"publishedDate": "2024-09-30",
"text": null,
"highlights": [
"Unfortunately, most vector databases treat metadata as a secondary feature, isolating it from the primary vector search process. As a result, handling queries that combine vectors and metadata can become a challenge, particularly when the search needs to account for a dynamic ran…",
"In the modern world of AI, managing vast amounts of data while keeping it relevant and accessible is a significant challenge, mainly when dealing with large language models (LLMs) and vector databases. One approach that has gained prominence in recent years is integrating vector …",
"As vector databases continue to evolve, future innovations that combine these two approaches more seamlessly will help improve data relevance and retrieval efficiency further.\n\n## Top comments (0)"
],
"highlightScores": [
0.9996404845034781,
0.9982059109067458,
0.9923103477753068
],
"prunedContext": "In the modern world of AI, managing vast amounts of data while keeping it relevant and accessible is a significant challenge, mainly when dealing with large language models (LLMs) and vector databases. One approach that has gained prominence in recent years is integrating vector …",
"textSource": "page",
"subQueries": [
"how do vector databases handle metadata filtering",
"how do vector databases handle metadata filtering documentation"
]
},
{
"id": "645ffb09-56cf-54a4-855f-56b10bd71cd0",
"url": "https://teachyou.ai/blog/vector-db-metadata-indexing",
"title": "Metadata Indexing in Vector Databases",
"score": 0.9994927941432675,
"publishedDate": "2026-06-20T10:48:36",
"text": null,
"highlights": [
"enforces permission-aware retrieval so a query never returns chunks the requesting user shouldn't see.\nlanguage\n: useful once you have multilingual content and want to avoid mixing languages in one result set.\nKeep field types simple and consistent. Most vector databases support …",
"If your actual matches are ranked 250th and 400th because the collection is dominated by other tenants' data, a top-200 fetch never surfaces them, and the user sees an empty result even though good matches exist.\nMetadata indexing solves this by pushing the filter into the search…",
"Deeply nested objects are less universally supported, so flatten your schema where you can.\nHow do I handle permission changes without re-embedding everything?\nStore access control as metadata (\naccess_level\n,\nallowed_roles\n) rather than baking it into the embedding, and update t…"
],
"highlightScores": [
0.9994927941432675,
0.9994493432406966,
0.9992573290294049
],
"prunedContext": "This article walks through why metadata indexing matters, how it works differently from a normal database index, the filtering strategies (pre-filter, post-filter, single-stage), and hands-on examples across Pinecone, Qdrant, Weaviate, and pgvector so you can pick the right patte…",
"textSource": "page",
"subQueries": [
"how do vector databases handle metadata filtering"
]
},
{
"id": "53766a5f-ce35-5100-aae5-b6891a0f597c",
"url": "https://oneuptime.com/blog/post/2026-01-30-metadata-filtering/view",
"title": "How to Implement Metadata Filtering",
"score": 0.9993117409815335,
"publishedDate": "2026-01-30T00:00:00",
"text": null,
"highlights": [
"## 2. Core Concepts and Architecture\n\n### The Two-Phase Search Model\n\nMany vector databases combine payload filtering with vector search using a query planner that can evaluate filters before, during, or after vector search depending on the engine and index:\n\n```mermaid\nflowchart…",
"*Building a RAG system or semantic search application? Ensure your infrastructure can handle the load with OneUptime - monitor your vector database performance, track query latencies, and get alerted before issues impact users.*\n\n### Related Reading\n\n- Vector Database Fundamental…",
"## 8. Implementation Examples\n\n### Complete RAG System with Filtering\n\n```python\nfrom typing import List, Dict, Any, Optional\nfrom dataclasses import dataclass\nfrom datetime import datetime, timezone\nimport hashlib\n\n@dataclass\nclass Document:\n id: str\n content: str\n embe…"
],
"highlightScores": [
0.9993117409815335,
0.9988643785436241,
0.9707108183522279
],
"prunedContext": "## 2. Core Concepts and Architecture\n\n### The Two-Phase Search Model\n\nMany vector databases combine payload filtering with vector search using a query planner that can evaluate filters before, during, or after vector search depending on the engine and index:\n\n```mermaid\nflowchart…",
"textSource": "page",
"subQueries": [
"how do vector databases handle metadata filtering example"
]
},
"… 4 more"
],
"costDollars": {
"total": 0.003,
"subQueries": 3
},
"searchTimeMs": 2589,
"searchTime": 2589,
"contextfetch": {
"tokens": {
"raw": 48376,
"pruned": 4358,
"savedPct": 90.99140069455929
},
"sources": {
"local": 9,
"google": 0,
"searxng": 214,
"bing": 0,
"wikipedia": 3,
"hn": 2
},
"cache": "miss",
"timingsMs": {
"recall": 1054,
"fetch": 870,
"rerank": 1288,
"total": 2589
}
}
}