GPT-Researcher Deep Dive
Executive Summary
GPT-Researcher is a production-grade autonomous research agent that takes a query, plans sub-queries, searches the web via multiple retrievers, scrapes and compresses content, and generates structured reports. It supports both a single-agent mode (the GPTResearcher class) and a multi-agent mode (LangGraph-based ChiefEditorAgent orchestration). The framework is designed for extensibility: pluggable LLM providers, retrievers, scrapers, and prompt families.
The architecture is fundamentally a plan โ search โ scrape โ compress โ synthesize pipeline. The single-agent mode is the "fast path" optimized for single queries, while the multi-agent mode adds human-in-the-loop review, parallel subtopic research, and reviewer/reviser cycles via LangGraph state machines.
Architecture Overview
Core Research Workflow
GPTResearcher Class
The GPTResearcher class in gpt_researcher/agent.py is the main entry point. It's a God Object that owns all subsystems:
class GPTResearcher:
def __init__(self, query, report_type, report_source, tone,
source_urls, document_urls, vector_store, ...):
self.cfg = Config(config_path)
self.memory = Memory(cfg.embedding_provider, cfg.embedding_model)
# Composed skills
self.research_conductor = ResearchConductor(self)
self.report_generator = ReportGenerator(self)
self.context_manager = ContextManager(self)
self.scraper_manager = BrowserManager(self)
self.source_curator = SourceCurator(self)
self.deep_researcher = DeepResearchSkill(self) # if DeepResearch
self.image_generator = ImageGenerator(self)
# MCP support
self.mcp_configs = mcp_configs
self.mcp_strategy = self._resolve_mcp_strategy(...)
The GPTResearcher instance is passed as self.researcher to every skill, making it a Mediator pattern. Each skill accesses the researcher's state directly. This creates tight coupling but simplifies state sharing across the pipeline.
Key Constructor Parameters
| Parameter | Type | Purpose |
|---|---|---|
query | str | The research question |
report_type | ReportType | research_report, resource_report, outline_report, custom_report, detailed_report, subtopic_report, deep |
report_source | ReportSource | web, local, azure, langchain_documents, langchain_vectorstore, hybrid |
tone | Tone enum | 17 tone options from Objective to Casual |
mcp_configs | list[dict] | MCP server configurations for tool-use research |
mcp_strategy | str | "fast" (once), "deep" (per sub-query), "disabled" |
source_urls | list[str] | Pre-defined URLs to scrape instead of searching |
vector_store | VectorStoreWrapper | External vector store for RAG |
Skills System
Skills are the decomposed capabilities of the researcher, each a class that takes the parent GPTResearcher as its constructor argument:
| Skill | File | Responsibility |
|---|---|---|
ResearchConductor |
skills/researcher.py | Plan sub-queries, orchestrate search + scrape + context gathering. Core of the research loop. |
ReportGenerator |
skills/writer.py | Write reports (intro, body, conclusion), manage subtopics for detailed reports. |
ContextManager |
skills/context_manager.py | Retrieve similar content via embedding compression, handle written-content dedup. |
BrowserManager |
skills/browser.py | Scrape URLs via WorkerPool, select top images, manage research sources. |
SourceCurator |
skills/curator.py | LLM-based source ranking by relevance, credibility, quantitative value. |
DeepResearchSkill |
skills/deep_research.py | Recursive breadthรdepth research with nested GPTResearcher instances. |
ImageGenerator |
skills/image_generator.py | Generate and embed images into reports (optional, pre-generation before writing). |
Memory & Embeddings
The Memory class (gpt_researcher/memory/embeddings.py) is a factory for LangChain embedding providers, not a conversational memory store. It creates the embedding instance used by ContextCompressor for similarity filtering.
Supported Embedding Providers (21)
class Memory:
def __init__(self, embedding_provider: str, model: str, **kwargs):
match embedding_provider:
case "openai":
from langchain_openai import OpenAIEmbeddings
_embeddings = OpenAIEmbeddings(model=model, **kwargs)
case "ollama":
from langchain_ollama import OllamaEmbeddings
_embeddings = OllamaEmbeddings(model=model, ...)
# ... 19 more providers
def get_embeddings(self):
return self._embeddings
Memory uses Python's match/case (structural pattern matching) with lazy imports โ only importing the required LangChain provider package when that provider is selected. This avoids installing all provider packages upfront.
Context Management
The ContextManager skill delegates to three compressor classes in gpt_researcher/context/compression.py:
1. ContextCompressor (Web Search Results)
class ContextCompressor:
def async_get_context(self, query, max_results=5, cost_callback=None):
# Optimization: skip compression for small content
if total_chars < COMPRESSION_THRESHOLD and len(docs) <= max_results:
return prompt_family.pretty_print_docs(direct_docs)
# Standard: split โ embed โ filter by similarity
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, overlap=100)
relevance_filter = EmbeddingsFilter(embeddings, threshold=0.35)
pipeline = DocumentCompressorPipeline([splitter, relevance_filter])
retriever = ContextualCompressionRetriever(pipeline, SearchAPIRetriever(pages))
2. VectorstoreCompressor (Pre-indexed Documents)
Simple similarity search on an existing vector store โ no compression pipeline needed.
3. WrittenContentCompressor (Dedup for Detailed Reports)
Finds similar previously-written sections to avoid repetition across subtopic reports. Uses the same splitโfilter approach but with SectionRetriever instead.
The ContextCompressor has a fast-path: if total content is under COMPRESSION_THRESHOLD (default 8000 chars) and doc count โค max_results, it skips the expensive embedding pipeline entirely and returns documents directly. This is significant for small research tasks.
Context Compression Pipeline
LLM Provider Layer
The LLM integration follows a three-tier model strategy:
| Tier | Config Key | Usage |
|---|---|---|
| Fast | FAST_LLM | Quick tasks (agent selection, summaries) |
| Smart | SMART_LLM | Report generation, source curation |
| Strategic | STRATEGIC_LLM | Sub-query generation, deep research planning |
GenericLLMProvider (llm_provider/generic/base.py)
class GenericLLMProvider:
@classmethod
def from_provider(cls, provider, chat_log=None, verbose=True, **kwargs):
if provider == "openai":
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(**kwargs)
elif provider == "anthropic":
from langchain_anthropic import ChatAnthropic
llm = ChatAnthropic(**kwargs)
# ... 25 more providers
return cls(llm, chat_log, verbose)
async def get_chat_response(self, messages, stream, websocket=None):
if not stream:
output = await self.llm.ainvoke(messages)
return output.content
else:
# Stream chunks via websocket
async for chunk in self.llm.astream(messages):
...
27 Supported LLM Providers
create_chat_completion Utility
The create_chat_completion() function in utils/llm.py is the central LLM call wrapper with:
- Retry logic: up to 10 attempts with exponential backoff (max 8s)
- Reasoning model support: auto-detects
o1/o3/o4-minimodels and disables temperature/max_tokens - Reasoning effort: configurable low/medium/high for supported models
- Cost tracking: estimates cost via
estimate_llm_cost()and callscost_callback - Streaming: websocket-based streaming for real-time report display
Retriever Architecture
GPT-Researcher supports 15 retriever backends, each implementing a .search() method:
| Retriever | Type | Notes |
|---|---|---|
| tavily | Search API | Default retriever |
| Search API | Google Custom Search | |
| bing | Search API | Bing Web Search |
| duckduckgo | Search API | Free, no API key needed |
| serper | Search API | Serper.dev API |
| serpapi | Search API | SerpAPI |
| searchapi | Search API | SearchAPI.io |
| searx | Self-hosted | SearXNG instance |
| arxiv | Academic | arXiv papers |
| semantic_scholar | Academic | AI-focused papers |
| pubmed_central | Academic | Biomedical literature |
| exa | Search API | Neural search |
| bocha | Search API | BoCha search |
| custom | User-defined | Custom retriever class |
| mcp | Tool-use | MCP server tools |
| xquik | Social | X/Twitter search |
Multi-Retriever Support
Multiple retrievers can be configured simultaneously (comma-separated). The system iterates through all retrievers for each sub-query and deduplicates URLs via the visited_urls set. Some retrievers (like PubMed Central) return full content directly, bypassing the scraping step.
# In _search_relevant_source_urls:
for retriever_class in self.researcher.retrievers:
if "mcpretriever" in retriever_class.__name__.lower():
continue # MCP handled separately
results = retriever.search(max_results=cfg.max_search_results_per_query)
for result in results:
if raw_content and len(raw_content) > 100:
prefetched_content.append(result) # Skip scraping
else:
new_search_urls.append(url) # Need scraping
MCP Integration
GPT-Researcher has first-class MCP (Model Context Protocol) support with a two-stage architecture:
MCP Client Manager
The MCPClientManager converts GPT-Researcher configs to langchain-mcp-adapters format, supporting stdio, websocket, and HTTP transports with automatic URL-based transport detection.
MCP Tool Selector
Uses the Strategic LLM to analyze available tools and pick the most relevant ones (default max 3). Falls back to pattern-matching on tool names if LLM selection fails.
MCP Research Skill
Binds selected tools to the LLM via llm.bind_tools(selected_tools), then invokes the LLM which autonomously decides which tools to call. Results are normalized into the standard {title, href, body} format.
Scraper System
Multiple scraper backends for content extraction:
| Scraper | Method | Best For |
|---|---|---|
| BeautifulSoup | Static HTML parsing | Fast, lightweight pages |
| Browser (Playwright/nodriver) | Headless browser | JS-rendered pages |
| Firecrawl | API-based | High-quality extraction |
| Tavily Extract | API-based | Quick extraction |
| PyMuPDF | PDF parsing | PDF documents |
| WebBaseLoader | LangChain loader | Standard web pages |
| ArXiv | arXiv-specific | Academic papers |
Scraping is parallelized via WorkerPool with configurable max_scraper_workers and scraper_rate_limit_delay.
Multi-Agent Architecture
The multi-agent system is a LangGraph state machine with specialized agent roles. It's in multi_agents/ (separate from the single-agent gpt_researcher/).
LangGraph Integration
The langgraph.json defines the graph entry point:
{
"python_version": "3.11",
"dependencies": ["./multi_agents"],
"graphs": {
"agent": "./multi_agents/agent.py:graph"
},
"env": ".env"
}
State Definitions
ResearchState (outer workflow):
class ResearchState(TypedDict):
task: dict
initial_research: str
sections: List[str]
research_data: List[dict]
human_feedback: str
title: str
headers: dict
date: str
table_of_contents: str
introduction: str
conclusion: str
sources: List[str]
report: str
DraftState (per-section sub-workflow):
class DraftState(TypedDict):
task: dict
topic: str
draft: dict
review: str
revision_notes: str
Workflow Construction
# ChiefEditorAgent._create_workflow:
workflow = StateGraph(ResearchState)
workflow.add_node("browser", agents["research"].run_initial_research)
workflow.add_node("planner", agents["editor"].plan_research)
workflow.add_node("researcher", agents["editor"].run_parallel_research)
workflow.add_node("writer", agents["writer"].run)
workflow.add_node("publisher", agents["publisher"].run)
workflow.add_node("human", agents["human"].review_plan)
workflow.add_edge('browser', 'planner')
workflow.add_edge('planner', 'human')
workflow.add_edge('researcher', 'writer')
workflow.add_edge('writer', 'publisher')
workflow.set_entry_point("browser")
workflow.add_edge('publisher', END)
# Human-in-the-loop conditional
workflow.add_conditional_edges('human',
lambda review: "accept" if review['human_feedback'] is None else "revise",
{"accept": "researcher", "revise": "planner"}
)
Agent Roles in Multi-Agent Mode
| Agent | Role | LLM Usage |
|---|---|---|
| ResearchAgent | Wraps GPTResearcher for initial + subtopic research | Uses GPTResearcher's full pipeline |
| EditorAgent | Plans section layout, runs parallel research per section | LLM for planning + creates per-section sub-graph |
| WriterAgent | Writes intro, conclusion, TOC, sources from research data | LLM with JSON output format |
| ReviewerAgent | Reviews drafts against guidelines, returns feedback or None | LLM with guidelines checking |
| ReviserAgent | Revises drafts based on reviewer feedback | LLM with revision instructions |
| PublisherAgent | Assembles final report, exports to MD/PDF/DOCX | No LLM โ pure formatting |
| HumanAgent | Collects human feedback on research plan | WebSocket or console input |
Deep Research Mode
The DeepResearchSkill implements a recursive breadth-first search over the research space:
Key Implementation Details
- Each depth level creates new GPTResearcher instances โ fully independent research sessions
- Breadth halves at each depth level (4 โ 2)
- Concurrency limited by
deep_research_concurrency(default 2) viaasyncio.Semaphore - Context is trimmed to 25,000 words to stay within LLM context limits
- MCP configuration is propagated to nested researchers
- Learnings and citations are accumulated across all depth levels
Detailed Report Mode
The detailed report mode (ReportType.DetailedReport) generates a multi-section report:
- conduct_research() โ initial broad research
- get_subtopics() โ LLM generates subtopic headers using
construct_subtopics() - For each subtopic:
- get_draft_section_titles() โ LLM generates H3 headers
- get_similar_written_contents() โ find related previous sections
- write_report() โ generate subtopic report with dedup awareness
- write_introduction() โ H1 + intro paragraph
- write_report_conclusion() โ summary + implications
The subtopic report prompt explicitly instructs the LLM to check existing headers and written contents, avoid duplication, and highlight differences when covering similar topics. This is reinforced by passing existing_headers and relevant_written_contents directly into the prompt.
Report Types
| Type | Enum Value | Description |
|---|---|---|
| ResearchReport | research_report | Standard comprehensive analysis |
| ResourceReport | resource_report | Bibliography-focused, analyzes each source |
| OutlineReport | outline_report | Structured outline with sections and key points |
| CustomReport | custom_report | User-defined query prompt |
| DetailedReport | detailed_report | Multi-section with subtopic decomposition |
| SubtopicReport | subtopic_report | Single subtopic section (internal use) |
| DeepResearch | deep | Recursive deep research with learnings + citations |
Report Source Options
| Source | Description |
|---|---|
| Web | Search + scrape from the internet |
| Local | Local document files |
| Azure | Azure Blob Storage documents |
| LangChainDocuments | Pre-loaded LangChain Document objects |
| LangChainVectorStore | Existing vector store for retrieval |
| Hybrid | Local docs + web search combined |
Configuration System
The Config class in gpt_researcher/config/config.py manages all settings with a layered override system:
Key Configuration Patterns
# LLM provider format: "provider:model"
FAST_LLM = "openai:gpt-4o-mini"
SMART_LLM = "openai:gpt-4o"
STRATEGIC_LLM = "openai:gpt-4o"
# Embedding format: "provider:model"
EMBEDDING = "openai:text-embedding-3-small"
# Retriever (comma-separated for multiple)
RETRIEVER = "tavily,google,arxiv"
# Deep research settings
DEEP_RESEARCH_BREADTH = 4
DEEP_RESEARCH_DEPTH = 2
DEEP_RESEARCH_CONCURRENCY = 2
# MCP strategy
MCP_STRATEGY = "fast" # fast | deep | disabled
Smart Type Conversion
The convert_env_value() method uses Python's typing hints to auto-convert environment variables: str โ string, int โ int, bool โ boolean ("true"/"1"/"yes"/"on"), List โ JSON parse.
Prompt Engineering
All prompts are defined as static methods on the PromptFamily class in gpt_researcher/prompts.py. This is a centralized prompt registry pattern.
Key Prompt Methods
| Method | Purpose | Notable Instructions |
|---|---|---|
generate_search_queries_prompt |
Generate sub-queries | Asks for Google-style queries, includes current date, context-aware refinement |
generate_report_prompt |
Main report writing | Must determine own opinion, markdown headers, APA format, hyperlink citations, min word count, no TOC |
generate_subtopic_report_prompt |
Subtopic sections | Must avoid duplicating existing headers/content, explain differences from existing content, H2/H3 only |
curate_sources |
Source curation | Prioritize quantitative data, retain broad perspectives, never rewrite content |
auto_agent_instructions |
Agent type selection | Return JSON with server name + role prompt, emoji-based categorization |
generate_deep_research_prompt |
Deep research synthesis | Synthesize hierarchical research, prioritize deeper-level insights, cross-branch connections |
generate_mcp_tool_selection_prompt |
MCP tool picking | Select exactly N tools, rank by relevance, return JSON with reasoning |
๐ View: generate_report_prompt (Main Report Writing)
๐ View: curate_sources (Source Curation)
Prompt Families
GPT-Researcher supports model-specific prompt families that override formatting for particular LLMs:
| Family | Target | Customization |
|---|---|---|
PromptFamily (Default) | General models | Standard markdown formatting |
GranitePromptFamily | IBM Granite (auto-detects version) | Dispatches to version-specific family |
Granite3PromptFamily | Granite 3.x | Custom document prefix/suffix tags <|start_of_role|>documents<|end_of_role|> |
Granite33PromptFamily | Granite 3.3 | Per-document ID tags with <|start_of_role|>document{"document_id": "..."} |
This is a Strategy Pattern for prompts. The get_prompt_family() factory function maps family names to classes. Each family must retain the same method signatures but can override implementations. This allows model-specific document formatting without changing agent logic.
Strengths
- Massive LLM/Provider Support: 27 LLM providers + 21 embedding providers with lazy imports and auto-install. The
provider:modelconfig format is clean and extensible. - Flexible Retriever System: 15 retriever backends with multi-retriever support, automatic content detection (skip scraping for full-content results), and MCP as a first-class retriever.
- Smart Context Compression: The embedding-based compression pipeline with a fast-path optimization for small content is well-designed. The similarity threshold is configurable.
- MCP Integration: Two-stage architecture (tool selection + execution) with strategy modes (fast/deep/disabled) is sophisticated and practical. Tool selection via LLM is innovative.
- Deep Research Mode: Recursive breadth-first search with configurable depth/breadth/concurrency is a powerful approach for exhaustive research. The word-limit trimming prevents context overflow.
- Multi-Agent Architecture: The LangGraph state machine with reviewer/reviser cycles and human-in-the-loop is a proper agentic workflow, not just a linear pipeline.
- Prompt Family Pattern: Model-specific prompt formatting (especially for IBM Granite) shows awareness that different models need different instruction formats.
- Source Curation: LLM-based source ranking with emphasis on quantitative data and broad perspectives adds a quality layer that most research agents skip.
- Cost Tracking: Per-step cost breakdown with
add_costs()callback throughout the pipeline. Essential for production use. - Streaming Support: WebSocket-based streaming for real-time report display in the web UI.
Weaknesses
- God Object Anti-Pattern:
GPTResearcheris 300+ lines with 30+ attributes, passed asself.researcherto every skill. Skills directly access and mutate the researcher's state. This creates tight coupling and makes testing difficult. - No Abstraction for Retriever Interface: Retrievers are discovered via
match/casestring matching and instantiated with varying constructor signatures. MCP retrievers need specialresearcher=param. No formalBaseRetrieverprotocol. - Dual Codebase Drift:
gpt_researcher/(single-agent) andmulti_agents/are separate codebases with duplicatedmemory/modules. The multi-agent system doesn't reuse the single-agent's skills โ it wraps the entireGPTResearcherclass. - MCP Name Detection Hack: Checking
"mcpretriever" in r.__name__.lower()to identify MCP retrievers is fragile. A proper type/protocol would be more maintainable. - Prompt Engineering in Python: All prompts are Python f-strings in static methods. No prompt versioning, no A/B testing, no prompt registry. Changing a prompt requires code changes.
- No Async Retriever Interface:
retriever.search()is synchronous (some are wrapped inasyncio.to_thread), creating inconsistency. Some paths call it directly, others useto_thread. - Auto-Install at Runtime:
_check_pkg()auto-installs missing LangChain provider packages viapip installat runtime. This is dangerous in production environments. - Incomplete Multi-Agent: The
ReviewerAgentandReviserAgentonly activate whenfollow_guidelines=True. TheHumanAgentonly works with WebSocket or console input โ no async feedback API. - Context Window Assumptions: The 25,000-word limit for deep research is hardcoded. The compression threshold (8000 chars) and similarity threshold (0.35) are set as defaults without model-aware calibration.
- Error Handling Gaps: The
SourceCurator.curate_sources()falls back to returning all sources on error (silent degradation). Thecreate_chat_completionretry loop can silently burn through API credits on persistent errors.
Design Patterns Identified
| Pattern | Usage | Quality |
|---|---|---|
| Mediator | GPTResearcher as central hub passing self to all skills | Convenient but coupled |
| Strategy | PromptFamily hierarchy for model-specific formatting | Well-applied |
| Factory | get_retriever(), GenericLLMProvider.from_provider(), Memory constructor | Extensive & practical |
| State Machine | LangGraph StateGraph for multi-agent workflow | Industry-standard |
| Pipeline | DocumentCompressorPipeline (split โ filter โ format) | Clean LangChain pattern |
| Observer | WebSocket streaming, log_handler, cost_callback | Good event system |
| Recursive | DeepResearchSkill.deep_research() with depth countdown | Functional but simple |
| Semaphore | Concurrency limiting in deep research and scraping | Proper async pattern |
| Lazy Import | Provider-specific imports only when needed | Reduces startup cost |
Final Verdict
GPT-Researcher is a feature-rich, production-viable research agent framework with impressive breadth of integration. Its greatest strength is the "batteries included" approach โ 27 LLM providers, 15 retrievers, 7 scrapers, MCP support, and multi-agent orchestration all out of the box.
The architecture is pragmatic rather than elegant. The GPTResearcher God Object and the dual single-agent/multi-agent codebases show the framework grew organically. The prompt engineering is functional but not sophisticated โ all prompts are f-strings in Python with no versioning or evaluation framework.
For teams building research agents, GPT-Researcher provides an excellent starting point and reference implementation. Its plugin architecture (retrievers, scrapers, LLM providers) is genuinely extensible. However, teams needing production reliability should invest in: (1) replacing the God Object with proper dependency injection, (2) adding structured logging and observability, (3) implementing prompt versioning, and (4) unifying the single-agent and multi-agent codepaths.
| Dimension | Rating | Notes |
|---|---|---|
| Extensibility | โญโญโญโญโญ | Pluggable everything โ retrievers, LLMs, scrapers, prompts |
| Architecture | โญโญโญ | Functional but God Object + dual codebase |
| Code Quality | โญโญโญ | Well-documented, but inconsistent patterns and runtime auto-install |
| MCP Integration | โญโญโญโญ | Sophisticated two-stage approach with strategy modes |
| Multi-Agent | โญโญโญ | Good LangGraph implementation, but drifts from single-agent code |
| Production Readiness | โญโญโญ | Cost tracking, streaming, retries โ but lacks observability and structured errors |
| Prompt Engineering | โญโญโญ | Comprehensive prompts but hardcoded in Python, no evaluation |
| Documentation | โญโญโญโญ | Good docstrings, type hints, README โ but no architecture docs |
Generated by Agent Deep Dive Analysis โข Source: gpt-researcher v0.14.7 โข Date: 2026-05-17