从 GPT Researcher 学习 LangChain

从 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 也支持 Claude
  • ToolMessage 把工具结果注入对话,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() --- 直接把 LangChain Document 列表写入向量库
  • 这种封装使得可以随意切换后端(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 WebBaseLoaderBSHTMLLoader
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 的 ChatModelEmbeddings 实现

六、小测验

阅读完代码后,检查自己是否能回答这些问题:

  1. 为什么项目用 GenericLLMProvider.from_provider() 而不是直接 new ChatOpenAI()
  2. create_chat_completion() 中的重试机制是怎么实现的?
  3. LangChain Chain 管道的 | 操作返回的是什么类型?
  4. 使用 bind_tools() 和手写 OpenAI function calling 格式有什么区别?
  5. ContextualCompressionRetriever 的压缩管道包含哪些步骤?
  6. LangGraph 的 StateGraph 和 LangChain 的 AgentExecutor 适用场景有什么不同?
  7. 项目中为什么没有使用 LangChain 的 Agent 框架,而是自建了 GPTResearcher
  8. 项目支持 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 的理解会远超教程水平。

相关推荐
吃饱了得干活1 小时前
LangChain 模型调用方案:invoke、stream、batch 的同步与异步
langchain·llm
取地址符1 小时前
Rust学习笔记(基于Rustlings)(2):数据结构与集合
笔记·学习·rust
心中有国也有家1 小时前
AtomGit Flutter 鸿蒙客户端:呼吸球动画
学习·flutter·华为·harmonyos
进阶的猪1 小时前
Linux 内核移植过程学习
linux·学习
SteveSenna2 小时前
aubo学习:设置TCP
学习
茯苓gao2 小时前
嵌入式开发笔记:工业机器人通信协议深度解析——从现场总线到工业以太网
笔记·嵌入式硬件·学习·机器人·信息与通信
取地址符2 小时前
Rust语法学习 (基于Rustlings)(1):基础语法
经验分享·笔记·学习·rust
绝世番茄3 小时前
VisualEffect 视觉特效鸿蒙HarmonyOS ArkTS原生学习
学习·华为·harmonyos·鸿蒙
xiaoduzi19913 小时前
ConcurrentHashMap学习
java·学习