
1. DeepResearcher 深度研究 Agent 到底解决什么问题DeepResearcher 是一个把「检索—阅读—分析—写报告」串成一条流水线的深度研究 Agent。你给它一个研究主题它会自己拆子问题、调搜索工具、打开网页读正文、抓 arXiv 论文、最后流式吐出一份带引用来源的 Markdown 报告。适合谁做行业调研的产品同学、需要快速摸清一个技术方向的开发者、以及想给自己的 Agent 项目加「深度研究」能力的工程师。我试过用普通 RAG 做调研最大的痛点是检索回来的片段是碎的模型只能基于几段摘要瞎编遇到需要跨多个网页对比的场景就露馅。DeepResearcher 的思路不一样它把「浏览器自动化」当成一等公民——不是只拿搜索引擎的摘要而是真的用 browser-use 打开页面、渲染、抽取正文再把结构化内容喂给模型。这样报告里的论据是能追溯到具体 URL 的。整个系统拆成四块各司其职模块文件职责MCP 工具服务器research_server.py注册 web_search、browse_url、search_arxiv、LinkReader 等工具Agent 推理编排graph.py基于 LangGraph 的多阶段状态机流式 API 服务client_server.pyFastAPI StreamingResponse 输出报告前端交互streamlit_app.py输入主题、选深度、实时看报告关键点在于 MCPModel Context Protocol这层抽象。它把每个工具函数用装饰器注册成标准接口模型通过函数调用动态选择工具而不是把工具逻辑硬编码进 prompt。这意味着你加一个新工具只要写个 Python 函数注册一下Agent 就能「即插即用」地学会用它。browser-use 则负责真实世界的网页交互——渲染 JS、模拟滚动、抽取正文突破传统爬虫拿不到动态内容的限制。推理链路走的是四阶段理解任务 → 信息检索 → 内容分析 → 报告生成。每个阶段是 LangGraph 里的一个节点节点之间通过 AgentState 传递数据。这个设计的好处是每步可观测、可中断、可替换模型。下面我从环境准备开始一步步带你把这套东西跑起来。2. 前置准备模型接入与 MCP 工具链配置在写任何 Agent 代码之前先把模型接入这关过了。DeepResearcher 支持 OpenAI、DeepSeek、Doubao 等主流模型统一走 OpenAI 兼容接口。我用 TaoToken 做模型接入层原因是它同时提供 Claude、GPT、DeepSeek 等模型的兼容端点切换模型只改一个 model 字段不用动代码结构。先拿 Key。打开 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite 创建 API Key然后在控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 确认余额和可用模型列表。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有各语言的调用示例。环境变量这样配写进.env# .env OPENAI_API_KEYsk-你的TaoToken密钥 OPENAI_BASE_URLhttps://taotoken.net/api DEFAULT_MODELclaude-sonnet-4-5-20250929注意 Base URL 是https://taotoken.net/api不带任何路径后缀。很多 401 报错就是因为把/v1重复拼了两遍。Python 依赖装这些pip install fastapi uvicorn streamlit requests \ langgraph langchain langchain-openai \ mcp browser-use arxiv docling python-dotenvbrowser-use 底层依赖 Playwright首次使用要装浏览器内核playwright install chromium如果你在服务器上跑没有图形界面加--with-deps装系统依赖。这一步踩过的坑是Chromium 版本和 Playwright 版本不匹配会报Executable doesnt exist解决办法是pip install -U playwright后重新playwright install。MCP 工具服务器用 FastMCP 起它本质是个独立的进程通过 stdio 或 SSE 和 Agent 通信。DeepResearcher 里用的是 stdio 模式Agent 启动时把工具服务器作为子进程拉起。这样工具和 Agent 解耦你可以单独调试工具也可以把工具服务器部署到另一台机器。模型这块再强调一下深度研究任务对模型的指令遵循和长上下文能力要求高。我实测下来Claude 系列在「按格式输出 JSON 工具调用参数」这件事上最稳DeepSeek 性价比高适合跑量。你可以在.env里配多个模型让不同阶段用不同模型——比如任务分解用便宜模型报告生成用强模型。TaoToken 的模型对话入口 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 可以先手动测一下模型对工具调用格式的遵循度确认没问题再写进代码。3. 可复制配置MCP 工具注册与 Agent 工作流这一节给你能直接抄的配置。先看 MCP 工具服务器research_server.py的核心结构# research_server.py import json from mcp.server.fastmcp import FastMCP from browser_use import Agent from langchain_openai import ChatOpenAI import arxiv mcp FastMCP(research-tools) llm ChatOpenAI( modelclaude-sonnet-4-5-20250929, base_urlhttps://taotoken.net/api, api_keysk-你的密钥, ) mcp.tool( nameweb_search, descriptionSearch the web for information on a specific topic using browser automation. ) async def web_search(query: str, num_results: int 5) - str: agent Agent( taskfSearch for {query}. Find {num_results} high-quality sources. fFor each, extract title, URL, and relevant content. fReturn a JSON array with title,url,content,source fields., llmllm, ) result await agent.run(max_steps10) return json.dumps(result, ensure_asciiFalse) mcp.tool( namesearch_arxiv, descriptionSearch arXiv for academic papers on a topic. ) async def search_arxiv(query: str, max_results: int 5) - str: client arxiv.Client() search arxiv.Search(queryquery, max_resultsmax_results, sort_byarxiv.SortCriterion.Relevance) papers [] for r in client.results(search): papers.append({ title: r.title, url: r.entry_id, summary: r.summary, pdf: r.pdf_url, }) return json.dumps(papers, ensure_asciiFalse) if __name__ __main__: mcp.run(transportstdio)每个mcp.tool装饰的函数name 和 description 就是模型看到的工具元数据。description 写得越清楚模型选工具越准。返回结构统一成 JSON 字符串方便 Agent 解析。再看 LangGraph 工作流graph.py# graph.py from typing import TypedDict, List, Dict, Any from langgraph.graph import StateGraph, END from langchain_core.messages import BaseMessage, HumanMessage, AIMessage class AgentState(TypedDict): messages: List[BaseMessage] research_data: List[Dict[str, Any]] report: str def understand_task(state: AgentState) - AgentState: last state[messages][-1].content if state[messages] else prompt f把研究主题拆成3-5个子问题输出JSON数组{last} resp model.invoke([HumanMessage(contentprompt)]) state[messages].append(AIMessage(contentresp.content)) state[research_data] [] return state def collect_data(state: AgentState) - AgentState: # 根据子问题调用 MCP 工具结果塞进 research_data ... return state def analyze_data(state: AgentState) - AgentState: ... return state def generate_report(state: AgentState) - AgentState: ... return state def make_graph(): wf StateGraph(AgentState) wf.add_node(understand_task, understand_task) wf.add_node(collect_data, collect_data) wf.add_node(analyze_data, analyze_data) wf.add_node(generate_report, generate_report) wf.add_edge(understand_task, collect_data) wf.add_edge(collect_data, analyze_data) wf.add_edge(analyze_data, generate_report) wf.add_edge(generate_report, END) wf.set_entry_point(understand_task) return wf.compile()如果你用 Claude Code 或 Cline 这类工具做开发MCP 配置要写全三件套。以 Cline 的 MCP 配置为例settings.json里这样写{ mcpServers: { research-tools: { command: python, args: [research_server.py], env: { OPENAI_API_KEY: sk-你的TaoToken密钥, OPENAI_BASE_URL: https://taotoken.net/api, DEFAULT_MODEL: claude-sonnet-4-5-20250929 } } } }Base URL、Key、Model ID 三件套缺一不可。Codex 用户则在~/.codex/auth.json里配{ OPENAI_API_KEY: sk-你的TaoToken密钥, OPENAI_BASE_URL: https://taotoken.net/api, model: claude-sonnet-4-5-20250929 }CC Switch 用户注意切换配置时确认 Base URL 没被覆盖成默认的 OpenAI 地址否则会 401。4. 验证请求从检索到报告生成的完整跑通配置写完跑一次完整流程验证。先单独测 MCP 工具服务器能不能起来python research_server.pystdio 模式下它不会打印东西正常现象。用 MCP Inspector 测更直观npx modelcontextprotocol/inspector python research_server.py浏览器打开 Inspector 界面能看到注册的工具列表点web_search填个 query 试跑。如果返回 JSON 数组说明工具链通了。然后起 API 服务client_server.py# client_server.py from fastapi import FastAPI from fastapi.responses import StreamingResponse from pydantic import BaseModel import json app FastAPI() class ResearchRequest(BaseModel): topic: str depth: str medium format: str markdown app.post(/research) async def research(req: ResearchRequest): async def stream_report(): graph make_graph() state {messages: [HumanMessage(contentreq.topic)], research_data: [], report: } async for event in graph.astream(state): if generate_report in event: report event[generate_report][report] for chunk in report: yield chunk yield f\nENDOFTEXTSTREAM\n{json.dumps({sources: []})} return StreamingResponse(stream_report(), media_typetext/plain)启动uvicorn client_server:app --host 0.0.0.0 --port 8000发一个测试请求curl -N -X POST http://localhost:8000/research \ -H Content-Type: application/json \ -d {topic:MCP协议在Agent工具调用中的优势,depth:medium,format:markdown}-N关掉缓冲你能看到报告一段段流出来。成功的话输出里会有结构化的 Markdown 报告最后跟一个ENDOFTEXTSTREAM标记和 sources JSON。前端 Streamlit 起法streamlit run streamlit_app.py打开http://localhost:8501输入主题选深度点生成。报告会实时渲染生成完能下载 Markdown。整个链路跑通大概需要 1-3 分钟取决于研究深度和工具调用次数。验证成功的标志有三个一是流式输出没有卡死二是报告里有具体 URL 引用三是 sources JSON 能解析。如果报告是空的但流没断多半是generate_report节点没拿到research_data检查collect_data的返回值有没有正确写进 state。5. 常见报错排查401、local proxy failed 与 choices 解析这一节把真实会撞上的报错列出来对照着改。401 Unauthorized。最常见。原因通常是 Base URL 写错或 Key 失效。检查三点Base URL 必须是https://taotoken.net/api不能带/v1Key 有没有多余空格环境变量有没有被系统里其他OPENAI_API_KEY覆盖。用这个命令快速验证curl https://taotoken.net/api/v1/models \ -H Authorization: Bearer sk-你的密钥返回模型列表就说明 Key 和地址都对。local proxy failed / connection refused。browser-use 起 Chromium 时连不上。原因一般是 Playwright 内核没装或版本不匹配。解决playwright install --force chromium如果还报错检查有没有设置HTTP_PROXY之类的环境变量指向了不可用的地址清掉再试。reading choices of undefined。这是 OpenAI 兼容接口返回结构不对导致的。模型返回的不是标准 chat completion 格式代码里resp.choices[0]就炸了。排查方法打印原始响应。resp model.invoke([HumanMessage(contentprompt)]) print(resp)如果返回的是字符串而不是对象说明模型端点不兼容。换一个模型 ID 试试或者确认 Base URL 指向的是兼容端点。TaoToken 的模型对话页面可以手动测同一个模型对比返回格式。OAuth / authentication_error。Claude Code 或 Codex 这类工具报这个通常是 auth.json 或 settings.json 里的字段名不对。Codex 要的是OPENAI_API_KEY不是api_keyClaude Code 的配置在~/.claude/settings.json字段是env.ANTHROPIC_BASE_URL和env.ANTHROPIC_API_KEY。三件套Base URL Key Model ID必须同时存在且拼写正确。MCP 工具调用超时。browser-use 跑网页任务默认可能超过 30 秒。在 Agent 初始化时加超时参数agent Agent(task..., llmllm, ) result await asyncio.wait_for(agent.run(max_steps10), timeout120)同时把max_steps调小避免模型在网页上无限点。报告生成到一半断了。流式接口里异常没捕获。在stream_report里包一层 try/except把错误信息也 yield 出去前端至少能看到原因try: async for chunk in ...: yield chunk except Exception as e: yield f\n[ERROR] {str(e)}排障时优先看服务端日志uvicorn 默认会打印 traceback。前端只显示「生成失败」的话去终端找完整堆栈。6. 长期编码与 Agent 扩展把 DeepResearcher 用起来跑通一次只是开始。真正要把它用起来得考虑长期编码和 Agent 扩展。如果你打算持续迭代这类深度研究 AgentCoding Plan 比按量付费更划算适合高频调用模型做工具编排的场景入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite。扩展新工具很简单照着research_server.py的模式加函数mcp.tool( namefetch_pdf, descriptionDownload and parse a PDF from a URL into markdown text. ) async def fetch_pdf(url: str) - str: from docling.document_converter import DocumentConverter conv DocumentConverter() result conv.convert(url) return result.document.export_to_markdown()注册完重启工具服务器Agent 下次推理时就能看到这个新工具。description 写清楚输入输出模型才知道什么时候该用它。性能上几个实测有效的优化把collect_data阶段的多个工具调用改成并行用asyncio.gather同时跑 web_search 和 search_arxiv能省一半时间给工具结果加缓存同一个 URL 不重复抓报告生成阶段用流式用户感知的等待时间大幅缩短。最后说个工程习惯把每个阶段的中间结果落盘。research_data存成 JSON报告存成 Markdown出问题时能复现。深度研究 Agent 的调试成本主要在「模型为什么选了这个工具」把推理链路和工具调用日志打全比事后猜要高效得多。