Building DeepResearch from Scratch (Part 2): Architecture Design and Implementation with LangGraph
This article walks through the design and implementation of a multi‑agent DeepResearch application using the Pipeline‑Agent pattern with LangGraph and LangChain, detailing three agents for task planning, web search via Tavily, and report generation, and provides complete Python code and test results.
Architecture Overview
The DeepResearch pipeline is composed of three sequential agents:
Task‑planning agent – expands a user query into multiple research angles and extracts keywords.
Web‑search agent – uses the Tavily search tool to retrieve and summarise web content for each keyword.
Report‑generation agent – aggregates the collected information and produces a structured long‑form markdown report.
Task‑Planning Agent
Implemented with LangChain because the output structure is simple.
Create a .env file and add DEEPSEEK_API_KEY.
Install dependencies: pydantic, langchain, langgraph, langchain_deepseek, langchain_tavily.
Define a system prompt that asks the model to generate 5‑7 search terms for a given query.
PLANNER_INSTRUCTIONS = (
"You are a helpful research assistant, Given a query, come up with a set of web searches "
"to perform to best answer the query, Output between 5 and 7 terms to query for."
)
planner_prompt = ChatPromptTemplate.from_messages([
("system", PLANNER_INSTRUCTIONS),
("human", "{query}")
])Structured output models:
class WebSearchItem(BaseModel):
query: str # The search term to use for the web search.
reason: str # Reason why this search is important to the query.
class WebSearchPlan(BaseModel):
searches: list[WebSearchItem]The planner chain is created with .with_structured_output(WebSearchPlan). A test with the query “请问你对AI+教育有何看法” returns seven structured keywords.
Web‑Search Agent
Built with LangGraph’s create_react_agent to combine the Tavily tool and the language model.
Add TAVILY_API_KEY to .env.
Define a prompt that requires a concise 2‑3 paragraph summary (<300 words) for each term.
SEARCH_INSTRUCTIONS = (
"You are a research assistant. Given a search term, you search the web and "
"produce a concise summary of the results. The summary must be 2‑3 paragraphs and "
"less than 300 words. Capture the main points. Write succinctly, no need for perfect "
"grammar. This will be consumed by someone synthesising a report, so capture the "
"essence and ignore fluff. Do not add any additional commentary."
)
search_tool = TavilySearch(max_results=5, topic="general")
search_agent = create_react_agent(model, prompt=SEARCH_INSTRUCTIONS, tools=[search_tool])A test using the first keyword from the planner shows the agent retrieving five webpages and summarising them.
Report‑Writing Agent
Implemented with LangChain because no external tool calls are required.
Define a writer prompt that first asks for an outline then a detailed markdown report (10‑20 pages, ≥1500 words).
Create a ReportData Pydantic model with fields short_summary, markdown_report, and follow_up_questions.
WRITER_PROMPT = (
"You are a senior researcher tasked with writing a cohesive report for a research query. "
"You will be provided with the original query and some initial research done by a research assistant. "
"First, produce an outline describing the structure and flow. Then generate the full report. "
"The final output should be markdown, lengthy and detailed (10‑20 pages, at least 1500 words)."
)
writer_prompt = ChatPromptTemplate.from_messages([
("system", WRITER_PROMPT),
("human", "{query}")
])
writer_chain = writer_prompt | model.with_structured_output(ReportData)End‑to‑End Integration
Helper functions wire the three agents together:
# Generate keyword plan
def plan_searches(query: str) -> WebSearchPlan:
return planner_chain.invoke({"query": query})
# Perform a single web search
def search(item: WebSearchItem) -> str | None:
try:
final_query = f"Search Item: {item.query}
Reason for searching: {item.reason}"
result = search_agent.invoke({"messages": [{"role": "user", "content": final_query}]})
return str(result['messages'][-1].content)
except Exception:
return None
# Search all keywords
def perform_searches(plan: WebSearchPlan):
results = []
for item in plan.searches:
res = search(item)
if res is not None:
results.append(res)
return results
# Write the final report
def write_report(query: str, search_results) -> ReportData:
summary = "".join(search_results)
final_query = f"Original query: {query}
Summarized search results: {summary}"
return writer_chain.invoke({"query": final_query})
# Full pipeline
def deepresearch(query: str) -> ReportData:
plan = plan_searches(query)
results = perform_searches(plan)
report = write_report(query, results)
print(report.markdown_report)
return reportRunning deepresearch('AI在教育方面的应用场景') produces a structured, content‑rich markdown report.
Conclusion
The end‑to‑end test confirms that the pipeline correctly executes task planning, multi‑angle web retrieval, and report synthesis, demonstrating the effectiveness of multi‑agent collaboration. Future extensions may include richer search capabilities (e.g., local document or knowledge‑base search) and deployment as a web service.
Fun with Large Models
Master's graduate from Beijing Institute of Technology, published four top‑journal papers, previously worked as a developer at ByteDance and Alibaba. Currently researching large models at a major state‑owned enterprise. Committed to sharing concise, practical AI large‑model development experience, believing that AI large models will become as essential as PCs in the future. Let's start experimenting now!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
