从 GPT Researcher 学习 LangChain
为什么选用这个项目学习 LangChain?
GPT Researcher 是一个"深度研究代理"(Deep Research Agent),它完整展示了 LangChain + LangGraph 在实际 Agent 产品中的方方面面:
- 调用 30+ 种不同 LLM(OpenAI、Claude、DeepSeek、本地 Ollama......)
- 集成 20+ 搜索/数据源(Tavily、DuckDuckGo、Bing、PubMed......)
- 多级研究流程(规划 → 并行搜索 → 内容提取 → 去重 → 生成报告)
- LangGraph 多 Agent 编排(编辑、研究员、审稿、修订、发布流水线)
- Tool Calling(函数调用) + Model Context Protocol (MCP)
- 向量存储 + Embedding + 上下文压缩
- WebSocket 实时流式响应
这是一个真实上线的生产项目,不是 demo/教程代码。
一、项目整体架构
#mermaid-svg-KAEH9tw1KJVCaokv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KAEH9tw1KJVCaokv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KAEH9tw1KJVCaokv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KAEH9tw1KJVCaokv .error-icon{fill:#552222;}#mermaid-svg-KAEH9tw1KJVCaokv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KAEH9tw1KJVCaokv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KAEH9tw1KJVCaokv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KAEH9tw1KJVCaokv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KAEH9tw1KJVCaokv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KAEH9tw1KJVCaokv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KAEH9tw1KJVCaokv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KAEH9tw1KJVCaokv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KAEH9tw1KJVCaokv .marker.cross{stroke:#333333;}#mermaid-svg-KAEH9tw1KJVCaokv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KAEH9tw1KJVCaokv p{margin:0;}#mermaid-svg-KAEH9tw1KJVCaokv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KAEH9tw1KJVCaokv .cluster-label text{fill:#333;}#mermaid-svg-KAEH9tw1KJVCaokv .cluster-label span{color:#333;}#mermaid-svg-KAEH9tw1KJVCaokv .cluster-label span p{background-color:transparent;}#mermaid-svg-KAEH9tw1KJVCaokv .label text,#mermaid-svg-KAEH9tw1KJVCaokv span{fill:#333;color:#333;}#mermaid-svg-KAEH9tw1KJVCaokv .node rect,#mermaid-svg-KAEH9tw1KJVCaokv .node circle,#mermaid-svg-KAEH9tw1KJVCaokv .node ellipse,#mermaid-svg-KAEH9tw1KJVCaokv .node polygon,#mermaid-svg-KAEH9tw1KJVCaokv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KAEH9tw1KJVCaokv .rough-node .label text,#mermaid-svg-KAEH9tw1KJVCaokv .node .label text,#mermaid-svg-KAEH9tw1KJVCaokv .image-shape .label,#mermaid-svg-KAEH9tw1KJVCaokv .icon-shape .label{text-anchor:middle;}#mermaid-svg-KAEH9tw1KJVCaokv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KAEH9tw1KJVCaokv .rough-node .label,#mermaid-svg-KAEH9tw1KJVCaokv .node .label,#mermaid-svg-KAEH9tw1KJVCaokv .image-shape .label,#mermaid-svg-KAEH9tw1KJVCaokv .icon-shape .label{text-align:center;}#mermaid-svg-KAEH9tw1KJVCaokv .node.clickable{cursor:pointer;}#mermaid-svg-KAEH9tw1KJVCaokv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KAEH9tw1KJVCaokv .arrowheadPath{fill:#333333;}#mermaid-svg-KAEH9tw1KJVCaokv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KAEH9tw1KJVCaokv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KAEH9tw1KJVCaokv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KAEH9tw1KJVCaokv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KAEH9tw1KJVCaokv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KAEH9tw1KJVCaokv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KAEH9tw1KJVCaokv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KAEH9tw1KJVCaokv .cluster text{fill:#333;}#mermaid-svg-KAEH9tw1KJVCaokv .cluster span{color:#333;}#mermaid-svg-KAEH9tw1KJVCaokv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KAEH9tw1KJVCaokv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KAEH9tw1KJVCaokv rect.text{fill:none;stroke-width:0;}#mermaid-svg-KAEH9tw1KJVCaokv .icon-shape,#mermaid-svg-KAEH9tw1KJVCaokv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KAEH9tw1KJVCaokv .icon-shape p,#mermaid-svg-KAEH9tw1KJVCaokv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KAEH9tw1KJVCaokv .icon-shape .label rect,#mermaid-svg-KAEH9tw1KJVCaokv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KAEH9tw1KJVCaokv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KAEH9tw1KJVCaokv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KAEH9tw1KJVCaokv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 多 Agent 系统 multi_agents/
数据处理
搜索/检索 retrievers/
LangChain 集成层
核心引擎 gpt_researcher/
后端服务 backend/
用户层
CLI cli.py
Next.js 前端
REST + WebSocket API
FastAPI Server
backend/server/app.py
WebSocket Manager
ChatAgentWithMemory
backend/chat/chat.py
GPTResearcher
agent.py
ResearchConductor
skills/researcher.py
ContextManager
skills/context_manager.py
ReportGenerator
skills/writer.py
DeepResearchSkill
skills/deep_research.py
SourceCurator
skills/curator.py
ImageGenerator
skills/image_generator.py
GenericLLMProvider
llm_provider/generic/base.py
Tool Calling
utils/tools.py
LangChain Chain
utils/llm.py
MCP Client
mcp/client.py
20+ Retrievers
Tavily, DuckDuckGo, Bing...
MCP Retriever
Scraper
BS / Playwright / PyMuPDF
Document Loader
LangChain Document Loaders
Memory + Embeddings
memory/embeddings.py
Vector Store
vector_store/vector_store.py
Context Compression
context/compression.py
ChiefEditorAgent
agents/orchestrator.py
LangGraph StateGraph
7 Agents
Editor, Researcher, Writer,
Reviewer, Reviser, Publisher...
二、LangChain 概念在项目中的具体映射
2.1 LLM 抽象层 --- 统一调用 30+ 模型
核心文件 : gpt_researcher/llm_provider/generic/base.py
这是最值得细读的 LangChain 使用范例。GenericLLMProvider.from_provider() 是一个工厂方法 ,根据 provider 字符串动态选择对应的 LangChain ChatModel 子类:
python
# 所有 LLM 模型统一通过 LangChain 的接口调用
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_ollama import ChatOllama
from langchain_google_genai import ChatGoogleGenerativeAI
# ... 共 28 种 provider
class GenericLLMProvider:
@classmethod
def from_provider(cls, provider: str, **kwargs):
if provider == "openai":
llm = ChatOpenAI(**kwargs)
elif provider == "anthropic":
llm = ChatAnthropic(**kwargs)
elif provider == "ollama":
llm = ChatOllama(**kwargs)
# ...
return cls(llm)
关键设计模式:
llm.ainvoke(messages)--- 异步非流式调用llm.astream(messages)--- 异步流式调用,逐 chunk 推送llm.bind_tools(tools)--- 绑定工具函数
GenericLLMProvider LLM API (OpenAI/Anthropic...) LangChain ChatModel GenericLLMProvider.from_provider() 业务代码 GenericLLMProvider LLM API (OpenAI/Anthropic...) LangChain ChatModel GenericLLMProvider.from_provider() 业务代码 #mermaid-svg-9ZKlWRGbI1OkyLCb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9ZKlWRGbI1OkyLCb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9ZKlWRGbI1OkyLCb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9ZKlWRGbI1OkyLCb .error-icon{fill:#552222;}#mermaid-svg-9ZKlWRGbI1OkyLCb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9ZKlWRGbI1OkyLCb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9ZKlWRGbI1OkyLCb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9ZKlWRGbI1OkyLCb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9ZKlWRGbI1OkyLCb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9ZKlWRGbI1OkyLCb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9ZKlWRGbI1OkyLCb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9ZKlWRGbI1OkyLCb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9ZKlWRGbI1OkyLCb .marker.cross{stroke:#333333;}#mermaid-svg-9ZKlWRGbI1OkyLCb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9ZKlWRGbI1OkyLCb p{margin:0;}#mermaid-svg-9ZKlWRGbI1OkyLCb .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9ZKlWRGbI1OkyLCb text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-9ZKlWRGbI1OkyLCb .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9ZKlWRGbI1OkyLCb .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-9ZKlWRGbI1OkyLCb .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-9ZKlWRGbI1OkyLCb .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-9ZKlWRGbI1OkyLCb #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-9ZKlWRGbI1OkyLCb .sequenceNumber{fill:white;}#mermaid-svg-9ZKlWRGbI1OkyLCb #sequencenumber{fill:#333;}#mermaid-svg-9ZKlWRGbI1OkyLCb #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-9ZKlWRGbI1OkyLCb .messageText{fill:#333;stroke:none;}#mermaid-svg-9ZKlWRGbI1OkyLCb .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9ZKlWRGbI1OkyLCb .labelText,#mermaid-svg-9ZKlWRGbI1OkyLCb .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-9ZKlWRGbI1OkyLCb .loopText,#mermaid-svg-9ZKlWRGbI1OkyLCb .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-9ZKlWRGbI1OkyLCb .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9ZKlWRGbI1OkyLCb .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-9ZKlWRGbI1OkyLCb .noteText,#mermaid-svg-9ZKlWRGbI1OkyLCb .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-9ZKlWRGbI1OkyLCb .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9ZKlWRGbI1OkyLCb .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9ZKlWRGbI1OkyLCb .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9ZKlWRGbI1OkyLCb .actorPopupMenu{position:absolute;}#mermaid-svg-9ZKlWRGbI1OkyLCb .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-9ZKlWRGbI1OkyLCb .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9ZKlWRGbI1OkyLCb .actor-man circle,#mermaid-svg-9ZKlWRGbI1OkyLCb line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-9ZKlWRGbI1OkyLCb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop逐 chunk provider="openai", model="gpt-4o"ChatOpenAI(model="gpt-4o")llm instanceGenericLLMProvider(llm)get_chat_response(messages, stream=True)llm.astream(messages)streamchunkchunk.content实时推送 / 累积
学习要点 : 永远不要直接调用 OpenAI API,而是通过 LangChain ChatModel 接口。切换模型只需改一个环境变量。
2.2 LangChain Chain --- Prompt + Model + OutputParser
核心文件 : gpt_researcher/utils/llm.py (line 152-213)
这是项目中唯一使用 langchain | chain 管道的地方,用于构造子主题(Subtopics):
python
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
parser = PydanticOutputParser(pydantic_object=Subtopics)
prompt = PromptTemplate(
template=prompt_family.generate_subtopics_prompt(),
input_variables=["task", "data", "subtopics", "max_subtopics"],
partial_variables={"format_instructions": parser.get_format_instructions()},
)
# 经典的 LangChain LCEL 管道
chain = prompt | model | parser
# 异步执行
output = await chain.ainvoke({
"task": task,
"data": data,
# ...
})
这个模式展示了 LCEL (LangChain Expression Language) 的核心:
渲染错误: Mermaid 渲染失败: Parse error on line 2: graph LR Input{"task", "data"} -- ------------------^ Expecting 'TAGEND', 'STR', 'MD_STR', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'DIAMOND_START'
学习要点:
|管道操作符是 LCEL 的核心语法PydanticOutputParser自动把 LLM 的输出解析成 Pydantic 模型partial_variables注入format_instructions告诉 LLM 输出格式
2.3 Tool/Function Calling --- bind_tools()
核心文件 : gpt_researcher/utils/tools.py
这个文件展示了完整的 Tool Calling 生命周期:
python
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage, ToolMessage
from langchain_core.tools import tool
# 1. 创建 LangChain 工具
@tool
def search_tool(query: str) -> str:
"""Search for current events or online information"""
results = search_function(query)
return str(results)
# 2. 绑定工具到 LLM
llm_with_tools = llm.bind_tools(tools)
# 3. 第一次调用 --- LLM 决定是否调工具
response = await llm_with_tools.ainvoke(lc_messages)
# 4. 处理工具调用
if hasattr(response, 'tool_calls') and response.tool_calls:
for tool_call in response.tool_calls:
# 执行工具
tool_result = await tool.ainvoke(tool_call['args'])
# 添加工具结果到对话
lc_messages.append(ToolMessage(
content=str(tool_result),
tool_call_id=tool_call['id']
))
# 5. 第二次调用 --- LLM 根据工具结果生成最终回答
final_response = await llm_with_tools.ainvoke(lc_messages)
这个模式在实际 Chat 系统中使用 (backend/chat/chat.py):
搜索工具 LLM + bind_tools ChatAgentWithMemory 用户 搜索工具 LLM + bind_tools ChatAgentWithMemory 用户 #mermaid-svg-oyc8plrm6nErQDTQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-oyc8plrm6nErQDTQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-oyc8plrm6nErQDTQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-oyc8plrm6nErQDTQ .error-icon{fill:#552222;}#mermaid-svg-oyc8plrm6nErQDTQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-oyc8plrm6nErQDTQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-oyc8plrm6nErQDTQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-oyc8plrm6nErQDTQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-oyc8plrm6nErQDTQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-oyc8plrm6nErQDTQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-oyc8plrm6nErQDTQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-oyc8plrm6nErQDTQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-oyc8plrm6nErQDTQ .marker.cross{stroke:#333333;}#mermaid-svg-oyc8plrm6nErQDTQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-oyc8plrm6nErQDTQ p{margin:0;}#mermaid-svg-oyc8plrm6nErQDTQ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oyc8plrm6nErQDTQ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-oyc8plrm6nErQDTQ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-oyc8plrm6nErQDTQ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-oyc8plrm6nErQDTQ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-oyc8plrm6nErQDTQ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-oyc8plrm6nErQDTQ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-oyc8plrm6nErQDTQ .sequenceNumber{fill:white;}#mermaid-svg-oyc8plrm6nErQDTQ #sequencenumber{fill:#333;}#mermaid-svg-oyc8plrm6nErQDTQ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-oyc8plrm6nErQDTQ .messageText{fill:#333;stroke:none;}#mermaid-svg-oyc8plrm6nErQDTQ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oyc8plrm6nErQDTQ .labelText,#mermaid-svg-oyc8plrm6nErQDTQ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-oyc8plrm6nErQDTQ .loopText,#mermaid-svg-oyc8plrm6nErQDTQ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-oyc8plrm6nErQDTQ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-oyc8plrm6nErQDTQ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-oyc8plrm6nErQDTQ .noteText,#mermaid-svg-oyc8plrm6nErQDTQ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-oyc8plrm6nErQDTQ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oyc8plrm6nErQDTQ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oyc8plrm6nErQDTQ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-oyc8plrm6nErQDTQ .actorPopupMenu{position:absolute;}#mermaid-svg-oyc8plrm6nErQDTQ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-oyc8plrm6nErQDTQ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-oyc8plrm6nErQDTQ .actor-man circle,#mermaid-svg-oyc8plrm6nErQDTQ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-oyc8plrm6nErQDTQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt需要工具不需要工具 提问加载报告 + 向量检索上下文消息 + 工具定义判断是否需要工具?调用 search_tool(query)搜索结果融合搜索结果的回答直接回答流式输出
学习要点:
bind_tools()是 provider-agnostic 的 --- 同一个代码既支持 OpenAI 也支持 ClaudeToolMessage把工具结果注入对话,LLM 自动参考它生成最终回答- 项目同时支持原生 OpenAI function calling 格式 (
get_tools())和 LangChain 的@tool装饰器两种方式
2.4 向量存储 + Embedding
核心文件 : gpt_researcher/memory/embeddings.py, gpt_researcher/vector_store/vector_store.py
Embedding 提供者工厂模式和 LLM 一样,支持 16+ 种 embedding 模型:
python
# memory/embeddings.py
from langchain_openai import OpenAIEmbeddings
from langchain_cohere import CohereEmbeddings
from langchain_ollama import OllamaEmbeddings
class Memory:
def get_embeddings(self):
if provider == "openai":
return OpenAIEmbeddings(model=model)
elif provider == "cohere":
return CohereEmbeddings(model=model)
# ...
向量存储封装:
python
# vector_store/vector_store.py
from langchain_core.documents import Document
from langchain_community.vectorstores import VectorStore
from langchain_text_splitters import RecursiveCharacterTextSplitter
class VectorStoreWrapper:
def load(self, documents):
text_splitter = RecursiveCharacterTextSplitter(...)
docs = text_splitter.split_documents(documents)
self.vector_store = VectorStore.from_documents(docs, self.embeddings)
def query(self, query, k=5):
return self.vector_store.similarity_search(query, k=k)
学习要点:
RecursiveCharacterTextSplitter--- LangChain 的标准文本分割器VectorStore.from_documents()--- 直接把 LangChainDocument列表写入向量库- 这种封装使得可以随意切换后端(Chroma、FAISS、Pinecone 等)
2.5 上下文压缩 --- LangChain Classic Retriever
核心文件 : gpt_researcher/context/compression.py
python
from langchain_classic.retrievers import ContextualCompressionRetriever
from langchain_classic.retrievers.document_compressors import (
DocumentCompressorPipeline, EmbeddingsFilter
)
from langchain_core.documents import Document
from langchain_text_splitters import RecursiveCharacterTextSplitter
class ContextCompressor:
async def async_get_context(self, query, max_results=10):
# 1. 分割文档
splitter = RecursiveCharacterTextSplitter(...)
chunks = splitter.split_documents(documents)
# 2. 基于 Embedding 相似度过滤
embeddings_filter = EmbeddingsFilter(
embeddings=self.embeddings,
similarity_threshold=0.5
)
pipeline = DocumentCompressorPipeline(
transformers=[splitter, embeddings_filter]
)
compressor = ContextualCompressionRetriever(
base_compressor=pipeline,
base_retriever=SearchAPIRetriever(...)
)
# 3. 只返回最相关的内容
relevant_docs = await compressor.aget_relevant_documents(query)
#mermaid-svg-lLyy1x4ECbnNd26Q{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-lLyy1x4ECbnNd26Q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lLyy1x4ECbnNd26Q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lLyy1x4ECbnNd26Q .error-icon{fill:#552222;}#mermaid-svg-lLyy1x4ECbnNd26Q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lLyy1x4ECbnNd26Q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lLyy1x4ECbnNd26Q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lLyy1x4ECbnNd26Q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lLyy1x4ECbnNd26Q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lLyy1x4ECbnNd26Q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lLyy1x4ECbnNd26Q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lLyy1x4ECbnNd26Q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lLyy1x4ECbnNd26Q .marker.cross{stroke:#333333;}#mermaid-svg-lLyy1x4ECbnNd26Q svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lLyy1x4ECbnNd26Q p{margin:0;}#mermaid-svg-lLyy1x4ECbnNd26Q .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lLyy1x4ECbnNd26Q .cluster-label text{fill:#333;}#mermaid-svg-lLyy1x4ECbnNd26Q .cluster-label span{color:#333;}#mermaid-svg-lLyy1x4ECbnNd26Q .cluster-label span p{background-color:transparent;}#mermaid-svg-lLyy1x4ECbnNd26Q .label text,#mermaid-svg-lLyy1x4ECbnNd26Q span{fill:#333;color:#333;}#mermaid-svg-lLyy1x4ECbnNd26Q .node rect,#mermaid-svg-lLyy1x4ECbnNd26Q .node circle,#mermaid-svg-lLyy1x4ECbnNd26Q .node ellipse,#mermaid-svg-lLyy1x4ECbnNd26Q .node polygon,#mermaid-svg-lLyy1x4ECbnNd26Q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lLyy1x4ECbnNd26Q .rough-node .label text,#mermaid-svg-lLyy1x4ECbnNd26Q .node .label text,#mermaid-svg-lLyy1x4ECbnNd26Q .image-shape .label,#mermaid-svg-lLyy1x4ECbnNd26Q .icon-shape .label{text-anchor:middle;}#mermaid-svg-lLyy1x4ECbnNd26Q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lLyy1x4ECbnNd26Q .rough-node .label,#mermaid-svg-lLyy1x4ECbnNd26Q .node .label,#mermaid-svg-lLyy1x4ECbnNd26Q .image-shape .label,#mermaid-svg-lLyy1x4ECbnNd26Q .icon-shape .label{text-align:center;}#mermaid-svg-lLyy1x4ECbnNd26Q .node.clickable{cursor:pointer;}#mermaid-svg-lLyy1x4ECbnNd26Q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lLyy1x4ECbnNd26Q .arrowheadPath{fill:#333333;}#mermaid-svg-lLyy1x4ECbnNd26Q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lLyy1x4ECbnNd26Q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lLyy1x4ECbnNd26Q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lLyy1x4ECbnNd26Q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lLyy1x4ECbnNd26Q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lLyy1x4ECbnNd26Q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lLyy1x4ECbnNd26Q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lLyy1x4ECbnNd26Q .cluster text{fill:#333;}#mermaid-svg-lLyy1x4ECbnNd26Q .cluster span{color:#333;}#mermaid-svg-lLyy1x4ECbnNd26Q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-lLyy1x4ECbnNd26Q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lLyy1x4ECbnNd26Q rect.text{fill:none;stroke-width:0;}#mermaid-svg-lLyy1x4ECbnNd26Q .icon-shape,#mermaid-svg-lLyy1x4ECbnNd26Q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lLyy1x4ECbnNd26Q .icon-shape p,#mermaid-svg-lLyy1x4ECbnNd26Q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lLyy1x4ECbnNd26Q .icon-shape .label rect,#mermaid-svg-lLyy1x4ECbnNd26Q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lLyy1x4ECbnNd26Q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lLyy1x4ECbnNd26Q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lLyy1x4ECbnNd26Q :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 原始文档
RecursiveCharacterTextSplitter
切分成chunk
EmbeddingsFilter
相似度 > 0.5
压缩后的上下文
仅保留相关片段
用户查询
学习要点:
ContextualCompressionRetriever包装了一个检索器,在执行检索后自动压缩结果DocumentCompressorPipeline可以把多个 transformer 串联起来- 这种做法避免了把整篇文档塞进 LLM 上下文窗口
2.6 自定义 LangChain Retriever
核心文件 : gpt_researcher/context/retriever.py
python
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever
class SearchAPIRetriever(BaseRetriever):
"""自定义 Retriever,继承 LangChain 的 BaseRetriever"""
documents: list
embeddings: Any
def _get_relevant_documents(
self, query: str, *, run_manager: CallbackManagerForRetrieverRun
) -> list[Document]:
# 基于 embedding 相似度的自定义检索逻辑
return relevant_docs
这是 LangChain 的标准扩展点 --- 只需继承 BaseRetriever 并实现 _get_relevant_documents()。
2.7 LangGraph --- 多 Agent 工作流
核心文件 : multi_agents/agents/orchestrator.py, multi_agents/agents/editor.py, multi_agents/memory/research.py
这是项目中最复杂的 LangChain 用法,展示了完整的 LangGraph StateGraph 工作流。
#mermaid-svg-4tpcNboHmI7fFgzA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4tpcNboHmI7fFgzA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4tpcNboHmI7fFgzA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4tpcNboHmI7fFgzA .error-icon{fill:#552222;}#mermaid-svg-4tpcNboHmI7fFgzA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4tpcNboHmI7fFgzA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4tpcNboHmI7fFgzA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4tpcNboHmI7fFgzA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4tpcNboHmI7fFgzA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4tpcNboHmI7fFgzA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4tpcNboHmI7fFgzA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4tpcNboHmI7fFgzA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4tpcNboHmI7fFgzA .marker.cross{stroke:#333333;}#mermaid-svg-4tpcNboHmI7fFgzA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4tpcNboHmI7fFgzA p{margin:0;}#mermaid-svg-4tpcNboHmI7fFgzA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4tpcNboHmI7fFgzA .cluster-label text{fill:#333;}#mermaid-svg-4tpcNboHmI7fFgzA .cluster-label span{color:#333;}#mermaid-svg-4tpcNboHmI7fFgzA .cluster-label span p{background-color:transparent;}#mermaid-svg-4tpcNboHmI7fFgzA .label text,#mermaid-svg-4tpcNboHmI7fFgzA span{fill:#333;color:#333;}#mermaid-svg-4tpcNboHmI7fFgzA .node rect,#mermaid-svg-4tpcNboHmI7fFgzA .node circle,#mermaid-svg-4tpcNboHmI7fFgzA .node ellipse,#mermaid-svg-4tpcNboHmI7fFgzA .node polygon,#mermaid-svg-4tpcNboHmI7fFgzA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4tpcNboHmI7fFgzA .rough-node .label text,#mermaid-svg-4tpcNboHmI7fFgzA .node .label text,#mermaid-svg-4tpcNboHmI7fFgzA .image-shape .label,#mermaid-svg-4tpcNboHmI7fFgzA .icon-shape .label{text-anchor:middle;}#mermaid-svg-4tpcNboHmI7fFgzA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4tpcNboHmI7fFgzA .rough-node .label,#mermaid-svg-4tpcNboHmI7fFgzA .node .label,#mermaid-svg-4tpcNboHmI7fFgzA .image-shape .label,#mermaid-svg-4tpcNboHmI7fFgzA .icon-shape .label{text-align:center;}#mermaid-svg-4tpcNboHmI7fFgzA .node.clickable{cursor:pointer;}#mermaid-svg-4tpcNboHmI7fFgzA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4tpcNboHmI7fFgzA .arrowheadPath{fill:#333333;}#mermaid-svg-4tpcNboHmI7fFgzA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4tpcNboHmI7fFgzA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4tpcNboHmI7fFgzA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4tpcNboHmI7fFgzA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4tpcNboHmI7fFgzA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4tpcNboHmI7fFgzA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4tpcNboHmI7fFgzA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4tpcNboHmI7fFgzA .cluster text{fill:#333;}#mermaid-svg-4tpcNboHmI7fFgzA .cluster span{color:#333;}#mermaid-svg-4tpcNboHmI7fFgzA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4tpcNboHmI7fFgzA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4tpcNboHmI7fFgzA rect.text{fill:none;stroke-width:0;}#mermaid-svg-4tpcNboHmI7fFgzA .icon-shape,#mermaid-svg-4tpcNboHmI7fFgzA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4tpcNboHmI7fFgzA .icon-shape p,#mermaid-svg-4tpcNboHmI7fFgzA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4tpcNboHmI7fFgzA .icon-shape .label rect,#mermaid-svg-4tpcNboHmI7fFgzA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4tpcNboHmI7fFgzA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4tpcNboHmI7fFgzA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4tpcNboHmI7fFgzA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 接受
修订
强制接受
通过
不通过
ResearchState (TypedDict)
task: dict
sections: Liststr
research_data: Listdict
report: str
human_feedback: str
START
Browser
初始搜索
Planner/Editor
规划大纲
Human in the Loop
人工审核
Researcher
并行子课题研究
Writer
撰写报告
Fact Checker
事实核查
Visualizer
生成图表
Publisher
发布报告
END
创建 StateGraph 的代码 (multi_agents/agents/orchestrator.py):
python
from langgraph.graph import StateGraph, END
from ..memory.research import ResearchState
class ChiefEditorAgent:
def _create_workflow(self, agents):
workflow = StateGraph(ResearchState)
# 添加节点 --- 每个节点是一个 agent 函数
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("fact_checker", agents["fact_checker"].run)
workflow.add_node("visualizer", agents["visualizer"].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', 'fact_checker')
workflow.add_edge('visualizer', 'publisher')
workflow.set_entry_point("browser")
workflow.add_edge('publisher', END)
# 条件边 --- 根据 Human Feedback 分支
workflow.add_conditional_edges(
'human',
lambda state: "accept" if state['human_feedback'] is None
else "force_accept" if state['revisions_count'] >= MAX_REVISIONS
else "revise",
{
"accept": "researcher",
"force_accept": "researcher",
"revise": "planner"
}
)
# Fact Checker 循环
workflow.add_conditional_edges(
'fact_checker',
lambda draft: "accept" if draft.get("fact_check_notes") is None
else "revise",
{"accept": "visualizer", "revise": "writer"}
)
return workflow
State 定义 (multi_agents/memory/research.py):
python
from typing import TypedDict, List, Annotated
import operator
class ResearchState(TypedDict):
task: dict
initial_research: str
sections: List[str]
research_data: List[dict]
human_feedback: str
plan_revision_count: int
title: str
headers: dict
date: str
report: str
fact_check_notes: str
# ... etc
#mermaid-svg-8K0M404Q9s7AT0GP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8K0M404Q9s7AT0GP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8K0M404Q9s7AT0GP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8K0M404Q9s7AT0GP .error-icon{fill:#552222;}#mermaid-svg-8K0M404Q9s7AT0GP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8K0M404Q9s7AT0GP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8K0M404Q9s7AT0GP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8K0M404Q9s7AT0GP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8K0M404Q9s7AT0GP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8K0M404Q9s7AT0GP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8K0M404Q9s7AT0GP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8K0M404Q9s7AT0GP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8K0M404Q9s7AT0GP .marker.cross{stroke:#333333;}#mermaid-svg-8K0M404Q9s7AT0GP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8K0M404Q9s7AT0GP p{margin:0;}#mermaid-svg-8K0M404Q9s7AT0GP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8K0M404Q9s7AT0GP .cluster-label text{fill:#333;}#mermaid-svg-8K0M404Q9s7AT0GP .cluster-label span{color:#333;}#mermaid-svg-8K0M404Q9s7AT0GP .cluster-label span p{background-color:transparent;}#mermaid-svg-8K0M404Q9s7AT0GP .label text,#mermaid-svg-8K0M404Q9s7AT0GP span{fill:#333;color:#333;}#mermaid-svg-8K0M404Q9s7AT0GP .node rect,#mermaid-svg-8K0M404Q9s7AT0GP .node circle,#mermaid-svg-8K0M404Q9s7AT0GP .node ellipse,#mermaid-svg-8K0M404Q9s7AT0GP .node polygon,#mermaid-svg-8K0M404Q9s7AT0GP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8K0M404Q9s7AT0GP .rough-node .label text,#mermaid-svg-8K0M404Q9s7AT0GP .node .label text,#mermaid-svg-8K0M404Q9s7AT0GP .image-shape .label,#mermaid-svg-8K0M404Q9s7AT0GP .icon-shape .label{text-anchor:middle;}#mermaid-svg-8K0M404Q9s7AT0GP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8K0M404Q9s7AT0GP .rough-node .label,#mermaid-svg-8K0M404Q9s7AT0GP .node .label,#mermaid-svg-8K0M404Q9s7AT0GP .image-shape .label,#mermaid-svg-8K0M404Q9s7AT0GP .icon-shape .label{text-align:center;}#mermaid-svg-8K0M404Q9s7AT0GP .node.clickable{cursor:pointer;}#mermaid-svg-8K0M404Q9s7AT0GP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8K0M404Q9s7AT0GP .arrowheadPath{fill:#333333;}#mermaid-svg-8K0M404Q9s7AT0GP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8K0M404Q9s7AT0GP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8K0M404Q9s7AT0GP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8K0M404Q9s7AT0GP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8K0M404Q9s7AT0GP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8K0M404Q9s7AT0GP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8K0M404Q9s7AT0GP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8K0M404Q9s7AT0GP .cluster text{fill:#333;}#mermaid-svg-8K0M404Q9s7AT0GP .cluster span{color:#333;}#mermaid-svg-8K0M404Q9s7AT0GP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8K0M404Q9s7AT0GP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8K0M404Q9s7AT0GP rect.text{fill:none;stroke-width:0;}#mermaid-svg-8K0M404Q9s7AT0GP .icon-shape,#mermaid-svg-8K0M404Q9s7AT0GP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8K0M404Q9s7AT0GP .icon-shape p,#mermaid-svg-8K0M404Q9s7AT0GP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8K0M404Q9s7AT0GP .icon-shape .label rect,#mermaid-svg-8K0M404Q9s7AT0GP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8K0M404Q9s7AT0GP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8K0M404Q9s7AT0GP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8K0M404Q9s7AT0GP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} LangGraph 关键概念
add_node
add_edge
add_conditional_edges
StateGraph
状态图
Node
节点 = Agent 函数
Edge
边 = 流向
Conditional Edge
条件边 = 分支逻辑
学习要点:
StateGraph是 LangGraph 的核心,状态通过TypedDict定义- 每个
Node接收 state 并返回更新后的 state(字典的一部分) add_conditional_edges根据路由函数的返回值选择下一个节点- 这是 Human-in-the-Loop 的经典实现 --- 审核节点通过反馈控制流程
chain.ainvoke()启动整个工作流
2.8 文档加载器 --- LangChain Community
分散在多个文件中,展示了 LangChain 社区 Document Loader 的各类用法:
python
# 网页加载
from langchain_community.document_loaders import WebBaseLoader
loader = WebBaseLoader(url)
# PDF 加载
from langchain_community.document_loaders import PyMuPDFLoader
loader = PyMuPDFLoader(file_path)
# 学术搜索
from langchain_community.retrievers import ArxivRetriever
retriever = ArxivRetriever(query=query)
# HTML 加载
from langchain_community.document_loaders import BSHTMLLoader
loader = BSHTMLLoader(html_path)
渲染错误: Mermaid 渲染失败: Parse error on line 3: ...gChain Document
{page_content, metad -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'DIAMOND_START'
学习要点 : LangChain 把所有文档来源统一成 Document(page_content, metadata) 格式,后续的 splitter、vector store、retriever 都基于这个统一格式。
2.9 项目没有使用的 LangChain 功能
- Agent + AgentExecutor --- 项目没有使用 LangChain 的标准 Agent 框架(AgentExecutor),而是自建了
GPTResearcher类来实现 Agent 逻辑 - LangChain Hub --- 没有从 LangChain Hub 拉取 prompt
- LangServe --- 没有使用 LangServe 部署
- Callback --- 除了
CallbackManagerForRetrieverRun,较少使用 LangChain 的回调系统
这么做是合理的:项目需要细粒度的控制(WebSocket 实时推送、多轮状态管理、精确追踪成本),LangChain 的高级抽象反而会成为约束。
三、LangGraph 工作流全景
3.1 单 Agent 研究流程(默认模式)
WebSocket ReportGenerator ContextManager Scraper Retriever Retriever ResearchConductor Agent Creator GPTResearcher 用户 WebSocket ReportGenerator ContextManager Scraper Retriever Retriever ResearchConductor Agent Creator GPTResearcher 用户 #mermaid-svg-dQ10LZiqJiEkMWBw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dQ10LZiqJiEkMWBw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dQ10LZiqJiEkMWBw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dQ10LZiqJiEkMWBw .error-icon{fill:#552222;}#mermaid-svg-dQ10LZiqJiEkMWBw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dQ10LZiqJiEkMWBw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dQ10LZiqJiEkMWBw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dQ10LZiqJiEkMWBw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dQ10LZiqJiEkMWBw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dQ10LZiqJiEkMWBw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dQ10LZiqJiEkMWBw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dQ10LZiqJiEkMWBw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dQ10LZiqJiEkMWBw .marker.cross{stroke:#333333;}#mermaid-svg-dQ10LZiqJiEkMWBw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dQ10LZiqJiEkMWBw p{margin:0;}#mermaid-svg-dQ10LZiqJiEkMWBw .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dQ10LZiqJiEkMWBw text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-dQ10LZiqJiEkMWBw .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-dQ10LZiqJiEkMWBw .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-dQ10LZiqJiEkMWBw .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-dQ10LZiqJiEkMWBw .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-dQ10LZiqJiEkMWBw #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-dQ10LZiqJiEkMWBw .sequenceNumber{fill:white;}#mermaid-svg-dQ10LZiqJiEkMWBw #sequencenumber{fill:#333;}#mermaid-svg-dQ10LZiqJiEkMWBw #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-dQ10LZiqJiEkMWBw .messageText{fill:#333;stroke:none;}#mermaid-svg-dQ10LZiqJiEkMWBw .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dQ10LZiqJiEkMWBw .labelText,#mermaid-svg-dQ10LZiqJiEkMWBw .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-dQ10LZiqJiEkMWBw .loopText,#mermaid-svg-dQ10LZiqJiEkMWBw .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-dQ10LZiqJiEkMWBw .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-dQ10LZiqJiEkMWBw .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-dQ10LZiqJiEkMWBw .noteText,#mermaid-svg-dQ10LZiqJiEkMWBw .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-dQ10LZiqJiEkMWBw .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dQ10LZiqJiEkMWBw .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dQ10LZiqJiEkMWBw .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-dQ10LZiqJiEkMWBw .actorPopupMenu{position:absolute;}#mermaid-svg-dQ10LZiqJiEkMWBw .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-dQ10LZiqJiEkMWBw .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-dQ10LZiqJiEkMWBw .actor-man circle,#mermaid-svg-dQ10LZiqJiEkMWBw line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-dQ10LZiqJiEkMWBw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} par并行子查询 生成简介 → 各章节 → 结论 query, report_type初始化choose_agent(query)agent_role, role_promptconduct_research()plan_research() → 生成子查询search(sub_query_1)search(sub_query_2)搜索结果URLs搜索结果URLsbrowse_urls(URLs)内容get_similar_content_by_query()compression pipeline压缩后的上下文contextwrite_report(context)流式输出报告完整报告report
3.2 多 Agent LangGraph 工作流(高级模式)
PublisherAgent VisualizerAgent FactCheckerAgent WriterAgent ResearchAgent EditorAgent BrowserAgent ChiefEditorAgent main.py PublisherAgent VisualizerAgent FactCheckerAgent WriterAgent ResearchAgent EditorAgent BrowserAgent ChiefEditorAgent main.py #mermaid-svg-CPObDecBp1Wk7eft{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-CPObDecBp1Wk7eft .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CPObDecBp1Wk7eft .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CPObDecBp1Wk7eft .error-icon{fill:#552222;}#mermaid-svg-CPObDecBp1Wk7eft .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CPObDecBp1Wk7eft .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CPObDecBp1Wk7eft .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CPObDecBp1Wk7eft .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CPObDecBp1Wk7eft .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CPObDecBp1Wk7eft .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CPObDecBp1Wk7eft .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CPObDecBp1Wk7eft .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CPObDecBp1Wk7eft .marker.cross{stroke:#333333;}#mermaid-svg-CPObDecBp1Wk7eft svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CPObDecBp1Wk7eft p{margin:0;}#mermaid-svg-CPObDecBp1Wk7eft .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-CPObDecBp1Wk7eft text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-CPObDecBp1Wk7eft .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-CPObDecBp1Wk7eft .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-CPObDecBp1Wk7eft .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-CPObDecBp1Wk7eft .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-CPObDecBp1Wk7eft #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-CPObDecBp1Wk7eft .sequenceNumber{fill:white;}#mermaid-svg-CPObDecBp1Wk7eft #sequencenumber{fill:#333;}#mermaid-svg-CPObDecBp1Wk7eft #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-CPObDecBp1Wk7eft .messageText{fill:#333;stroke:none;}#mermaid-svg-CPObDecBp1Wk7eft .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-CPObDecBp1Wk7eft .labelText,#mermaid-svg-CPObDecBp1Wk7eft .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-CPObDecBp1Wk7eft .loopText,#mermaid-svg-CPObDecBp1Wk7eft .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-CPObDecBp1Wk7eft .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-CPObDecBp1Wk7eft .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-CPObDecBp1Wk7eft .noteText,#mermaid-svg-CPObDecBp1Wk7eft .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-CPObDecBp1Wk7eft .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-CPObDecBp1Wk7eft .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-CPObDecBp1Wk7eft .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-CPObDecBp1Wk7eft .actorPopupMenu{position:absolute;}#mermaid-svg-CPObDecBp1Wk7eft .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-CPObDecBp1Wk7eft .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-CPObDecBp1Wk7eft .actor-man circle,#mermaid-svg-CPObDecBp1Wk7eft line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-CPObDecBp1Wk7eft :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} par课题1课题2 alt通过不通过 loop事实核查 run_research_task()创建 LangGraph StateGraph初始搜索initial_research规划大纲sections, title并行子课题研究研究课题1研究课题2research_data撰写报告draft核查acceptrevise修订生成图表diagrams发布最终报告research_report
四、推荐学习路线和代码阅读顺序
第 1 阶段:基础概念 --- 理解 LangChain 的 LLM 抽象
| 步骤 | 文件 | 学习目标 |
|---|---|---|
| 1.1 | gpt_researcher/utils/llm.py |
create_chat_completion() 函数,重试 + 流式 + 成本追踪 |
| 1.2 | gpt_researcher/llm_provider/generic/base.py |
GenericLLMProvider.from_provider() 工厂,28 种 provider 接入 |
| 1.3 | 对照 LangChain 官方文档 | ChatOpenAI.ainvoke() vs astream() |
关键认知 : LangChain 的 ChatModel 是所有 LLM 交互的统一接口。项目封装了它但没有用 AgentExecutor。
第 2 阶段:LCEL 管道
| 步骤 | 文件 | 学习目标 |
|---|---|---|
| 2.1 | gpt_researcher/utils/llm.py:152-213 |
`PromptTemplate |
| 2.2 | gpt_researcher/prompts.py |
PromptFamily --- 多报告类型的 Prompt 管理 |
| 2.3 | 对照 LangChain 文档 | LCEL 的 ` |
关键认知 : 项目中只有子主题生成用了 LCEL 管道。其他地方的 LLM 调用都是直接 provider.get_chat_response()。这说明什么时候用管道、什么时候直接调要看具体需求。
第 3 阶段:Tool Calling
| 步骤 | 文件 | 学习目标 |
|---|---|---|
| 3.1 | gpt_researcher/utils/tools.py |
@tool 装饰器、bind_tools()、ToolMessage |
| 3.2 | backend/chat/chat.py |
Tool Calling 在 Chat 系统中的实际应用 |
| 3.3 | gpt_researcher/mcp/client.py |
MCP 协议 + Tool 发现 |
关键认知 : Tool Calling 的核心不是"调用函数",而是LLM 自主决定何时调用 。bind_tools() 让所有支持 function calling 的 provider 用同一套代码。
第 4 阶段:核心研究流程
| 步骤 | 文件 | 学习目标 |
|---|---|---|
| 4.1 | gpt_researcher/agent.py |
GPTResearcher 类的构造函数和 run() 方法 |
| 4.2 | gpt_researcher/skills/researcher.py |
ResearchConductor --- 规划 → 检索 → 收集 |
| 4.3 | gpt_researcher/skills/context_manager.py |
ContextManager --- 上下文压缩 |
| 4.4 | gpt_researcher/context/compression.py |
ContextCompressor --- LangChain Classic 压缩管道 |
| 4.5 | gpt_researcher/skills/curator.py |
SourceCurator --- 来源质量评估 |
| 4.6 | gpt_researcher/skills/deep_research.py |
DeepResearchSkill --- 多层深度搜索 |
关键认知 : 这是不使用 LangChain Agent 框架 而自建 Agent 的完整范例。ResearchConductor 相当于自建的 Agent 逻辑,比 LangChain AgentExecutor 提供更细粒度的控制。
第 5 阶段:向量存储 + Embedding
| 步骤 | 文件 | 学习目标 |
|---|---|---|
| 5.1 | gpt_researcher/memory/embeddings.py |
Embedding 工厂、16+ 种模型 |
| 5.2 | gpt_researcher/vector_store/vector_store.py |
VectorStoreWrapper --- 文档加载 + 分割 + 向量化 |
| 5.3 | backend/chat/chat.py |
RAG 在 Chat 中的应用 |
关键认知: 项目没有依赖于某个具体向量数据库,而是通过 LangChain 接口做了抽象。生产中可以轻松切换 Chroma/FAISS/Pinecone。
第 6 阶段:Document Loader + Retriever
| 步骤 | 文件 | 学习目标 |
|---|---|---|
| 6.1 | gpt_researcher/document/document.py |
LangChain WebBaseLoader、BSHTMLLoader |
| 6.2 | gpt_researcher/document/online_document.py |
在线文档加载 |
| 6.3 | gpt_researcher/retrievers/tavily/tavily_search.py |
一个具体 Retriever 的实现 |
| 6.4 | gpt_researcher/context/retriever.py |
自定义 BaseRetriever |
| 6.5 | gpt_researcher/scraper/ |
多种 Scraper 集成(BS、Playwright、PyMuPDF、Firecrawl) |
关键认知 : LangChain 把一切文档来源统一为 Document(page_content, metadata),这是所有后续处理的基础。
第 7 阶段:MCP (Model Context Protocol)
| 步骤 | 文件 | 学习目标 |
|---|---|---|
| 7.1 | gpt_researcher/mcp/client.py |
MultiServerMCPClient --- 连接 MCP 服务器 |
| 7.2 | gpt_researcher/mcp/research.py |
MCP 工具选择 + 调用 |
| 7.3 | gpt_researcher/retrievers/mcp/retriever.py |
MCP 作为 Retriever 集成 |
关键认知: MCP 是比传统 Tool Calling 更高级的协议,让 LLM 应用可以动态发现和使用外部工具。
第 8 阶段:LangGraph 多 Agent(高级)
| 步骤 | 文件 | 学习目标 |
|---|---|---|
| 8.1 | multi_agents/memory/research.py |
ResearchState --- 工作流状态定义 |
| 8.2 | multi_agents/agents/orchestrator.py |
ChiefEditorAgent._create_workflow() --- StateGraph 构建 |
| 8.3 | multi_agents/agents/editor.py |
EditorAgent --- 规划 + 并行研究 |
| 8.4 | multi_agents/agents/orchestrator.py:100-104 |
Fact Checker 条件边 |
| 8.5 | multi_agents/agents/orchestrator.py:89-97 |
Human-in-the-Loop 条件边 |
| 8.6 | multi_agents/main.py |
入口:chain.ainvoke() |
关键认知 : LangGraph 的 StateGraph 比 LangChain Agent 更适合确定性工作流。当流程图固定且有多种分支时(如人工审核循环),StateGraph 是正确答案。
第 9 阶段:整体俯瞰
| 步骤 | 文件 | 学习目标 |
|---|---|---|
| 9.1 | gpt_researcher/actions/ |
所有 action 函数的组织 |
| 9.2 | gpt_researcher/config/config.py |
完整配置系统 |
| 9.3 | gpt_researcher/prompts.py |
PromptFamily 管理所有提示词 |
| 9.4 | backend/server/app.py |
FastAPI + WebSocket 整合 |
| 9.5 | backend/server/websocket_manager.py |
WebSocket 事件驱动研究 |
五、各个 LangChain 包的作用
| 包 | 在项目中的用途 |
|---|---|
langchain-core |
BaseRetriever, Document, PromptTemplate, PydanticOutputParser, messages (HumanMessage、SystemMessage、AIMessage、ToolMessage), tool, RateLimiter |
langchain-openai |
ChatOpenAI (包括 Azure、DashScope、DeepSeek 等兼容 API) |
langchain-anthropic |
ChatAnthropic |
langchain-community |
ArxivRetriever, WebBaseLoader, BSHTMLLoader, PyMuPDFLoader, InMemoryVectorStore, ChatLiteLLM |
langchain-mcp-adapters |
MultiServerMCPClient --- 连接 MCP 服务器 |
langchain-text-splitters |
RecursiveCharacterTextSplitter |
langchain-classic |
ContextualCompressionRetriever, DocumentCompressorPipeline, EmbeddingsFilter |
langgraph |
StateGraph, END --- 多 Agent 工作流 |
| langchain-* (各 provider) | 对应 provider 的 ChatModel 和 Embeddings 实现 |
六、小测验
阅读完代码后,检查自己是否能回答这些问题:
- 为什么项目用
GenericLLMProvider.from_provider()而不是直接 newChatOpenAI()? create_chat_completion()中的重试机制是怎么实现的?- LangChain Chain 管道的
|操作返回的是什么类型? - 使用
bind_tools()和手写 OpenAI function calling 格式有什么区别? ContextualCompressionRetriever的压缩管道包含哪些步骤?- LangGraph 的
StateGraph和 LangChain 的AgentExecutor适用场景有什么不同? - 项目中为什么没有使用 LangChain 的 Agent 框架,而是自建了
GPTResearcher? - 项目支持 28 种 LLM provider 和 16+ 种 embedding provider,靠的是什么设计模式?
七、学习路线图总览
#mermaid-svg-PS4ehBY4Kpz89SwB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PS4ehBY4Kpz89SwB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PS4ehBY4Kpz89SwB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PS4ehBY4Kpz89SwB .error-icon{fill:#552222;}#mermaid-svg-PS4ehBY4Kpz89SwB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PS4ehBY4Kpz89SwB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PS4ehBY4Kpz89SwB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PS4ehBY4Kpz89SwB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PS4ehBY4Kpz89SwB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PS4ehBY4Kpz89SwB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PS4ehBY4Kpz89SwB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PS4ehBY4Kpz89SwB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PS4ehBY4Kpz89SwB .marker.cross{stroke:#333333;}#mermaid-svg-PS4ehBY4Kpz89SwB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PS4ehBY4Kpz89SwB p{margin:0;}#mermaid-svg-PS4ehBY4Kpz89SwB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PS4ehBY4Kpz89SwB .cluster-label text{fill:#333;}#mermaid-svg-PS4ehBY4Kpz89SwB .cluster-label span{color:#333;}#mermaid-svg-PS4ehBY4Kpz89SwB .cluster-label span p{background-color:transparent;}#mermaid-svg-PS4ehBY4Kpz89SwB .label text,#mermaid-svg-PS4ehBY4Kpz89SwB span{fill:#333;color:#333;}#mermaid-svg-PS4ehBY4Kpz89SwB .node rect,#mermaid-svg-PS4ehBY4Kpz89SwB .node circle,#mermaid-svg-PS4ehBY4Kpz89SwB .node ellipse,#mermaid-svg-PS4ehBY4Kpz89SwB .node polygon,#mermaid-svg-PS4ehBY4Kpz89SwB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PS4ehBY4Kpz89SwB .rough-node .label text,#mermaid-svg-PS4ehBY4Kpz89SwB .node .label text,#mermaid-svg-PS4ehBY4Kpz89SwB .image-shape .label,#mermaid-svg-PS4ehBY4Kpz89SwB .icon-shape .label{text-anchor:middle;}#mermaid-svg-PS4ehBY4Kpz89SwB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PS4ehBY4Kpz89SwB .rough-node .label,#mermaid-svg-PS4ehBY4Kpz89SwB .node .label,#mermaid-svg-PS4ehBY4Kpz89SwB .image-shape .label,#mermaid-svg-PS4ehBY4Kpz89SwB .icon-shape .label{text-align:center;}#mermaid-svg-PS4ehBY4Kpz89SwB .node.clickable{cursor:pointer;}#mermaid-svg-PS4ehBY4Kpz89SwB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PS4ehBY4Kpz89SwB .arrowheadPath{fill:#333333;}#mermaid-svg-PS4ehBY4Kpz89SwB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PS4ehBY4Kpz89SwB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PS4ehBY4Kpz89SwB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PS4ehBY4Kpz89SwB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PS4ehBY4Kpz89SwB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PS4ehBY4Kpz89SwB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PS4ehBY4Kpz89SwB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PS4ehBY4Kpz89SwB .cluster text{fill:#333;}#mermaid-svg-PS4ehBY4Kpz89SwB .cluster span{color:#333;}#mermaid-svg-PS4ehBY4Kpz89SwB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PS4ehBY4Kpz89SwB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PS4ehBY4Kpz89SwB rect.text{fill:none;stroke-width:0;}#mermaid-svg-PS4ehBY4Kpz89SwB .icon-shape,#mermaid-svg-PS4ehBY4Kpz89SwB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PS4ehBY4Kpz89SwB .icon-shape p,#mermaid-svg-PS4ehBY4Kpz89SwB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PS4ehBY4Kpz89SwB .icon-shape .label rect,#mermaid-svg-PS4ehBY4Kpz89SwB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PS4ehBY4Kpz89SwB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PS4ehBY4Kpz89SwB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PS4ehBY4Kpz89SwB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 第八阶段:LangGraph
multi_agents/memory/research.py
ResearchState
multi_agents/agents/orchestrator.py
ChiefEditorAgent
multi_agents/agents/
各 Agent 节点
第七阶段:MCP
mcp/client.py
MultiServerMCPClient
retrievers/mcp/
MCP Retriever
第六阶段:文档 + 检索
document/
Document Loaders
retrievers/
20+ providers
context/retriever.py
Custom BaseRetriever
第五阶段:向量 + RAG
memory/embeddings.py
Embedding 工厂
vector_store/vector_store.py
VectorStoreWrapper
context/compression.py
ContextCompressor
第四阶段:核心 Agent
agent.py
GPTResearcher
skills/researcher.py
ResearchConductor
skills/curator.py
SourceCurator
skills/deep_research.py
DeepResearchSkill
第三阶段:Tool Calling
utils/tools.py
bind_tools + @tool
backend/chat/chat.py
Chat 系统
第二阶段:LCEL 管道
utils/llm.py:152-213
PromptTemplate | Model | Parser
prompts.py
PromptFamily
第一阶段:LLM 抽象
utils/llm.py
create_chat_completion()
llm_provider/generic/base.py
GenericLLMProvider
第一阶段
第二阶段
第三阶段
第四阶段
第五阶段
第六阶段
第七阶段
第八阶段
建议: 每读完一个文件,在编辑器里打开它,实际搜索一下对应的 LangChain 官方文档,理解项目代码和官方 API 的对应关系。这个项目是对 LangChain 最真实的"生产级"使用,读完它,你对 LangChain 的理解会远超教程水平。