拓冰建站拓冰建站
首页 / 资讯中心 / 正文

多智能体工作流实战:构建AI深度研究助手

1. 这个项目到底在解决什么问题1.1 传统人工调研的三大痛点以及AI的切入点先把这个事情说透。Deep Research Assistant我最初的想法其实非常朴素工作里经常需要快速了解一个新行业、新竞品或者新技术领域传统做法是人肉打开搜索引擎一页一页翻搜索结果点开网页复制粘贴到笔记里再对着这些零散资料慢慢整理成报告。这个过程有多耗时做过咨询、行研、产品调研的朋友应该都有体会——单单是资料收集阶段就可能花掉半天真正动笔写报告的时候又会发现有些关键数据没找到还得回头再查。我遇到的痛点可以归纳成三条信息过载与筛选难。搜索一个关键词可能返回几百万条结果真正有参考价值的可能就前面三五页而且质量参差不齐。多轮检索的路径割裂。查完A资料去查B资料中间的逻辑衔接全靠人脑硬记经常查着查着忘了最初想问什么。输出整理的二次成本高。资料有了还得重新组织语言、梳理逻辑、标注出处本质上又是一轮脑力劳动。所以这个项目的切入点很明确让AI扮演一个“调研助理”的角色把用户给的一个大方向拆解成若干子问题逐个去检索、阅读、提炼最后汇总成一份带引用来源的结构化报告。它不是一个简单的问答机器人而是一个真正会“干活”的多智能体工作流。1.2 深度研究助手和普通聊天问答的本质区别很多人一开始会问这和直接问ChatGPT有什么区别区别很大。ChatGPT这类模型的知识截止日期是固定的你问它“2025年最新的充电桩行业数据”它往往只能给出一个训练数据范围内的通用回答甚至可能会一本正经地编造不存在的数字。但Deep Research Assistant的核心逻辑不是“从模型参数里回忆答案”而是“先检索真实世界的最新信息再让模型基于检索结果做分析和写作”。用大白话解释传统问答是让一个天才靠记忆答题而深度研究助手是让一个聪明的实习生先查资料、再动笔。前者的上限是训练数据的截止日期后者能检索到的信息都是实时数据只要网络上有相关内容理论上都能拿回来分析。另一个重要区别在于过程的拆解。一个合格的调研报告通常会经历确认选题、拆分维度、收集数据、交叉验证、组织语言、排版校对这几个阶段。Deep Research Assistant把这件事流程化了规划模块负责拆解任务检索模块负责收集素材综合模块负责写每个小节的正文审校模块负责检查遗漏和矛盾。整个链路清晰每个环节都可以单独调整和优化。1.3 这套系统是谁需要、谁适合用我在开发过程中顺手整理了一下目标用户画像大致分三类互联网从业者产品经理做竞品分析、运营做行业动态扫描、投资岗做标的研究这类人群最需要快速获取结构化信息。学术研究相关人员毕业设计初期的文献调研、科研前的方向预研虽然学术上有更专业的数据库但Google Scholar之外的网络公开信息也是重要补充。内容创作者和技术博主写深度文章之前需要大量背景素材一条命令跑出来一份带引用的素材包能节省大量前期准备时间。下面这张表是我反复思考和实测后整理出的核心差异点方便你判断自己是否真的需要这样一个工具对比维度普通问答式ChatDeep Research Assistant信息时效受限训练数据截止日期实时检索网络信息回答深度单轮单篇浅尝辄止多轮迭代按照提纲逐节产出引用溯源通常没有或模糊带来源链接和参考段落过程可控一个Prompt到底各阶段独立模块可人工干预适用场景日常咨询、临时问答深度调研、报告撰写、知识预研2. 核心思路与方案选型为什么要这样做2.1 为什么采用多智能体工作流而不是一个万能Prompt一开始我确实尝试过用一个超长的Prompt把“搜索和分析”全塞给模型。比如写一个3000字的系统提示词要求模型“思考用户需求自行搜索整理答案”。但实测效果很差主要有三个原因。第一是上下文窗口的限制。一次搜索可能要拿到大量网页原文把这些内容全部塞进一次模型调用里很快就超过上下文长度。即便强行塞进去模型也会被无关信息干扰导致重点抓取不准。第二是过程不可控。如果一切都在一个对话里完成用户很难干预“中间步骤”。比如模型检索方向跑偏了、使用的搜索关键词太泛你只能干看着最终答案稀烂无从纠正。而拆成独立模块后每一步都可以设定规则甚至可以在关键节点暂停人工介入调整。第三是成本不可控。一个长对话如果中途出错整个上下文可能都要重来浪费大量token。拆成模块后每个模块可以单独重试逻辑上清晰很多。既然决定了多模块拆解实现方式上又面临一个选择题用现成的Agent框架比如LangGraph、AutoGen还是自己写一个调度循环我最终选择了自己写一个轻量级的调度核心而不是直接套LangGraph。原因很实际这种调研类任务本质上是一个有向执行流——规划、检索、综合、审校每一步的输入输出都很明确不需要复杂的状态机管理。自己写的话可控性最强查错也容易。框架虽好但很多时候杀鸡用了牛刀调试成本反而更高。当然如果你后续要处理非常复杂的动态分支逻辑引入LangGraph这类框架是有价值的这是后话。2.2 技术选型的三个关键决策技术选型上我做了三个比较关键的决策直接把方案确定了下来。第一个决策是大模型的选型。核心诉求有两个一是中文和英文内容都要能处理得当二是token成本不能太高毕竟深度调研要跑很多轮检索和写作。我最后选了GLM-4-Flash为主力模型这是因为目前它有一个其他家大模型没有的优势——提供了公开且免费的API调用额度对于跑这种频繁调用的大任务非常友好。即便后续额度政策变化我也预留了模型层抽象可以随时无缝切换到其他家的接口。第二个决策是搜索引擎API的选择。最开始我试图直接用爬虫去请求某度搜索页面然后解析HTML里的结果。实测发现这种方式非常脆弱搜索引擎的页面结构稍微调整一下脚本就废了而且很容易触发反爬机制导致IP被临时限制。后来换成了Serper.dev提供的Google搜索API它的免费版每月有2500次调用额度对个人项目来说完全够用了。价格便宜、返回结构化JSON、稳定性好确实省心很多。第三个决策是网页正文提取使用Trafilatura库而不是我手写正则。爬虫容易把爬下来的HTML清洗成干净的正文反而最麻烦。Trafilatura这个库我在多个项目里用过它对新闻、博客、论坛页面的正文提取效果都相当不错一行代码就能拿到干净文本。相比自己写正则慢慢抠稳定性和效率都提升了一个量级。2.3 系统整体工作流拆解整个系统运行起来后的工作流我用大白话描述一下用户提交一个宽泛的问题比如“2025年充电桩行业分析”。规划模块出场编排出三到五个子调研方向例如市场规模、政策环境、龙头玩家、技术趋势、用户痛点每个方向还给出一组搜索关键词。每个子任务进入检索模块拿着关键词去搜索引擎拿结果列表然后逐条打开正文截取关键段落保存成“参考语料”。综合模块登场根据该子任务的提问约束把参考语料整理归纳成一段逻辑通顺、带引用标记的文字。若干子任务的产出拼在一起审校模块检查引用是否真实、整体结构是否完整如果有明显缺陷会触发一次重试。最终把所有子报告拼接成一个完整的Markdown文档保存到本地。这套流程跑一轮通常能在大约两到四分钟内产出一份基础的行业报告相比人工检索动辄半天的效率提升是非常明显的。3. 手把手实现从环境配置到核心代码3.1 环境准备与依赖安装在开始之前先把项目的骨架建好。我用的是Python 3.10虚拟环境工具用的Anaconda这是我现在所有Python项目标配了。conda create -n deep_research python3.10 conda activate deep_research pip install openai trafilatura httpx pydantic python-dotenv你需要准备两个API Key一个是模型供应商的API Key另一个是Serper的API Key。建议都写进项目根目录的.env文件不要在代码里硬编码。用python-dotenv加载非常方便。.env文件内容参考如下GLM_API_KEY你的模型API密钥 SERPER_API_KEY你的Serper密钥3.2 核心数据结构设计在写业务逻辑之前我先把数据模型定好了。这一个步骤看起来不起眼但对后续开发帮助极大——每个模块之间的数据流就靠这些结构衔接类型清晰了代码写起来不会乱。from pydantic import BaseModel from typing import List class SubTask(BaseModel): 子任务定义把大问题拆出来的一个独立调研方向 name: str # 子任务名称例如市场规模 instruction: str # 对这个子任务产出内容的具体要求 search_queries: List[str] # 对应的一组搜索关键词 class SearchResult(BaseModel): 单条搜索结果 title: str link: str snippet: str class SourceRecord(BaseModel): 一条已经从网页中提取出来的参考语料 url: str title: str content: str # 清洗后的正文片段 class SubReport(BaseModel): 子报告的产出后续会被拼进最终报告 task_name: str content: str references: List[str]这里我用Pydantic做数据模型一方面是类型校验另一方面是后续序列化保存中间结果非常方便。调试的时候能够直接dump成JSON看每一步产出的中间状态对排查问题帮助很大。3.3 规划模块把模糊需求拆成可执行的调研提纲规划模块要做的事说复杂也复杂说简单也简单——给大模型一个结构化输出要求让它把用户的宽泛问题拆成子任务。async def planner(client, research_question: str) - List[SubTask]: system_prompt 你是一个严谨的研究规划助手。你需要把用户提出的宽泛研究问题 拆解成3到5个独立的子调研任务。每个子任务必须包含 1. 明确的调研方向名称例如市场规模、竞争格局、技术趋势等 2. 对最终产出的内容要求需要使用中文描述 3. 三到五个适合搜索引擎检索的关键词尽量覆盖不同角度 请以JSON数组输出字段名为name、instruction、search_queries。 response await client.chat.completions.create( modelglm-4-flash, temperature0.2, response_format{type: json_object}, messages[ {role: system, content: system_prompt}, {role: user, content: research_question} ] ) data json.loads(response.choices[0].message.content) return [SubTask(**item) for item in data[tasks]]这里有几个实操细节值得你注意要求模型返回JSON并指定response_format会大幅降低解析出错的概率。如果不用这个参数模型偶尔会在输出里夹杂解释性文字解析必炸。temperature设为0.2保证规划结果相对稳定。规划任务不需要创造性稳定比惊喜更重要。搜索关键词设计得要有层次感。比如调研“充电桩行业”可以拆成行业规模类关键词、政策类关键词、技术类关键词、竞品财报关键词不同维度能搜到不同类型的资料。我见过一些人偷懒让规划模块只生成一个搜索词然后检索模块把所有内容怼回来这样信息密度太低最终报告质量很难保证。所以这一步的产出质量几乎决定了整个漏斗的流量入口质量。3.4 检索模块搜索、抓取、清洗三层递进检索模块是重头戏也是整个系统里最容易出问题的地方。它内部其实是三个子步骤调用搜索API拿结果列表、逐个网页抓取正文、从正文中截取与子任务相关的片段。先封装一个搜索函数async def web_search(serper_key: str, query: str, num_results: int 5): url https://google.serper.dev/search payload {q: query, num: num_results, gl: cn, hl: zh-cn} headers {X-API-KEY: serper_key, Content-Type: application/json} async with httpx.AsyncClient() as client: resp await client.post(url, jsonpayload, headersheaders) data resp.json() results [] for item in data.get(organic, []): results.append(SearchResult( titleitem.get(title, ), linkitem.get(link, ), snippetitem.get(snippet, ) )) return results拿到搜索结果列表之后下一步就是网页正文提取。这部分我强烈建议直接用Trafilatura库而不是手动解析HTML。原因前面也说过了手动解析面对不同网站的不同页面结构写出来的规则非常脆弱。Trafilatura基于算法自动识别页面主内容区域对绝大部分新闻、博客、企业官网都适用。import trafilatura def extract_main_content(url: str) - str: downloaded trafilatura.fetch_url(url) if not downloaded: return result trafilatura.extract(downloaded, include_commentsFalse, include_tablesTrue, output_formattxt) return result or 这里有两个小坑一是include_tables要设为True很多行业数据其实是在表格里的不提取会丢失重要信息二是有些网页会返回较长正文需要做长度控制。检索模块拿到的正文可能会非常长几万字的文章直接塞给模型做综合成本高且噪声大。我建议对每条网页正文做截断处理优先保留开头部分和包含关键词的段落这个逻辑也很简单用关键词索引定位然后切片就行。3.5 综合模块让模型把语料写成结构化小节检索完成之后手里是一堆来源链接和正文片段。综合模块的任务就是让模型扛着这些“素材”写出一段高质量的、带引用的小节文字。async def synthesize(client, task: SubTask, sources: List[SourceRecord]) - SubReport: source_text for idx, src in enumerate(sources): source_text f\n\n【参考{idx1}】来源: {src.url}\n{src.content[:1500]} user_prompt f请根据以下参考材料完成一项子调研。 子任务{task.name} 具体要求{task.instruction} 可用参考材料 {source_text} 写作要求 1. 输出应该像一篇行业分析报告的小章节有观点、有论据不使用列表式流水账 2. 在提到数据或关键事实的地方用[ref:1]这样的标记标注对应参考来源 3. 如果参考材料相互矛盾如实说明差异不要强行调和 4. 控制在500到800字之间使用中文 response await client.chat.completions.create( modelglm-4-flash, temperature0.4, messages[ {role: system, content: 你是一个资深行业研究员。你善于从多个信息源中提炼关键事实并形成逻辑清晰的中文分析报告。}, {role: user, content: user_prompt} ] ) content response.choices[0].message.content return SubReport( task_nametask.name, contentcontent, references[s.url for s in sources] )这段代码里值得留意的一个设计是我把每条参考材料都加了编号并要求模型在写正文的时候用[ref:1]这种标记去对应。这一步非常关键——它让输出的报告天然具备引用溯源能力。即便模型偶尔写出来的数字和原网页稍有出入读者也可以顺着标记回到原文去核实这比没有引用的AI生成内容可信度高了一个档次。3.6 审校模块和主控循环审校模块的核心功能是检查综合模块产出的内容是否存在明显的“幻觉”和“不完整”。实际操作中我主要让它做三件事检查引用标记是否都对应到了真实参考源URL、检查内容是否满足子任务的指令要求、检查是否有明显前后矛盾。主控循环的伪代码大致是这样async def run_research(question: str): subtasks await planner(client, question) final_report [] for task in subtasks: # 遍历每个子任务的所有搜索词汇总检索结果 collected_sources [] for query in task.search_queries: results await web_search(SERPER_KEY, query, num_results5) for r in results: content extract_main_content(r.link) if len(content) 200: # 过滤掉打不开或正文太短的页面 collected_sources.append(SourceRecord( urlr.link, titler.title, contentcontent )) # 去重同一域名下可能搜出来好几篇相似内容 seen_urls set() unique_sources [] for s in collected_sources: if s.url not in seen_urls: seen_urls.add(s.url) unique_sources.append(s) report await synthesize(client, task, unique_sources[:5]) final_report.append(report) print(f已完成子任务: {task.name}) await generate_final_markdown(final_report)这段代码里我特别留了个去重逻辑。实践中发现同一个站点的多篇文章有时候内容高度雷同不去重的话会让综合模块参考到大量重复信息影响产出多样性。限制每个子任务最多使用5条高质量语料也是经过实测的成本与效果平衡点——少于3条素材不足多于7条则模型容易抓到冗余信息。3.7 最终报告的拼接与保存最终报告我会拼成一个规范的Markdown文档包括标题、导语、各小节正文、参考链接附录。因为每个子报告已经带了引用标记整理起来不费劲直接用Python字符串拼接即可。保存为带时间戳的文件名便于回溯查看不同版本的产出差异。4. 实测实录跑通一个完整调研任务的复盘4.1 测试用例与运行数据为了验证系统效果我选了一个真实场景做测试分析2025年新能源汽车充电桩行业的主要趋势和市场格局。这是一个比较宽泛的问题适合测试规划器的拆解能力。设定参数模型glm-4-flash每个搜索词取前5条搜索结果每个子任务最多用5条参考语料最大抓取长度单篇2000字运行过程记录阶段实际耗时说明规划拆解约12秒生成4个子任务子任务1检索综合约35秒搜索了3个关键词抓取17条网页提取并综合子任务2检索综合约30秒两个关键词打不开程序自动跳过空结果子任务3检索综合约41秒有一个网站返回403自动忽略子任务4检索综合约38秒正常完成报告拼接约3秒生成最终Markdown文件整体跑完耗时为2分39秒。API费用方面由于使用免费额度的模型几乎是零成本运行。如果换成GPT-4o这类付费模型同样的运行大概消耗1到2美元换算下来性价比也是可以接受的。4.2 各阶段输出质量评估与分析规划模块产出的四个子任务分别为行业市场规模与发展现状、政策环境与补贴动向、技术路线演进趋势、竞争格局与主要玩家动态。这个拆解相对专业覆盖面也很全而且给出的搜索关键词组合也很有层次感这说明规划模型确实理解了“分析一个行业”的基本维度。综合模块产出的正文质量让人惊喜。其中一个子报告的片段是这样的国内充电桩市场规模在2024年继续保持高速增长态势。根据多份行业公开数据和券商研报显示截至2024年底全国充电基础设施累计数量已突破千万台级别公共充电桩和私人充电桩的结构比例持续优化。与新能源汽车保有量增速相比车桩比仍存在一定缺口这既是行业痛点也意味着后续增长空间。部分地区充电桩利用率偏低的问题仍然突出运营商盈利模式尚未完全成熟。这段文字有数据、有分析、有结论直接放进一份正式报告里也毫不违和。而我最终在测试报告中抽查了引用标记确认标记确实对应到了真实的来源链接。4.3 效果验收哪些场景值得用它实测后我的结论是Deep Research Assistant最适合的场景是“快速建立对一个陌生话题的基础认知框架”。在信息真实性和时效性要求不高的探索阶段它可以充当一个非常得力的信息预处理器。但如果用在一线城市特定楼盘的投资分析、主板上市公司财务细节核查、小众技术专利检索这类对信息准确性有极高要求的场景我仍然会建议人工逐条核实来源。毕竟任何检索型Agent都不可能完全避免模型在归纳环节引入的主观判断偏差。5. 高频问题与正确避坑姿势5.1 三大高频坑点与我的排查方案踩坑一搜索结果重复率过高参考材料同质化严重。这是最开始跑测试时最常遇到的问题。同一个平台的内容经常被多个站转载搜索结果里70%都指向同一个来源的缝缝补补版本。解决办法是我在检索阶段加入了去重逻辑同时限定每个子任务最多用5条语料强迫模型在多样性有限的材料中做取舍和提炼反而让分析更有深度。踩坑二网页抓取失效异常处理不到位导致任务中断。网站超时、403拒绝、返回内容过短这些都是家常便饭。刚开始我的代码里没有完善的异常处理任何一个站点出问题整体任务就会崩溃。后来我加了一个约定抓取失败的标准是返回内容不足200字符这样的URL直接跳过不影响后续流程。实测下来稳定性从60%提升到95%以上。踩坑三模型输出JSON解析失败。调用glm-4-flash时我强制指定了response_format为json_object但偶尔仍然会遇到模型中英文混杂、引号转义的问题。我的处理方案是每次解析失败后自动重试一次并把错误信息反馈给模型让它修正。加了这个自愈逻辑之后解析失败率几乎归零。5.2 提示词里的三个隐藏细节提示词设计是这个项目里最值得反复打磨的部分。我总结下来有三个细节直接影响最终产出质量。第一写作要求的颗粒度要足够细。只说了“写一篇分析报告”是远远不够的必须明确是“像行业研究报告的小章节有观点、有论据不使用列表式流水账”模型才会摆脱罗列要点的惯性。第二对矛盾信息的处理策略要提前约定。不同网页对同一事件的统计口径可能完全不同。如果不给模型指令它往往会强行选一个数字或者含糊其辞。我在提示词里明确要求“如果参考材料相互矛盾如实说明差异不要强行调和”这个指令让最终报告的可信度提升了很多。第三引用标记必须写入写作指令。让模型逐条标注[ref:n]引用原理其实利用的是模型的“溯源强迫症”——一旦要求它提供来源它在编写内容时就会自觉减少自由发挥的比例输出明显更保守也更可靠。5.3 这套系统后续还能往哪些方向长现在的版本已经能完成基础的调研报告生成但距离一个成熟的产品还有不少空间。我梳理了几个投入产出比比较高的扩展方向一是引入浏览器自动化。目前只能依赖搜索API返回的摘要和网页正文遇到信息藏在交互式页面里比如需要点击展开的统计图表就抓取不到。后续如果引入Playwright这类工具可以模拟真实用户浏览行为通过点击、滚动、翻页拿到更丰富的动态内容信息覆盖范围会大很多。二是增加情报记忆层。目前每次调研都是“一次性”任务查过的东西下次再查又会重新搜索一遍。如果引入一个本地向量库把历次调研中挖掘到的语料做嵌入存储下一次检索时可以优先在历史资料里查重不仅能节省API调用还能形成持续的行业知识积累。三是任务调度的并行化。当前的实现是串行处理子任务的明显浪费了墙钟时间。改成asyncio.gather并行跑四个子任务后单次调研耗时能压缩到一分钟以内体验会再上一个台阶。另外一个很有意思的方向是让AI“追问”用户。比如用户在调研提纲生成之后AI可以反问一句“你更关注技术角度还是商业角度”然后根据回答动态调整检索权重。这个功能我已经在设计原型了目前在prompt层做了简单实现后续会把它做成更灵活的交互反馈机制。最后再分享一条我从这个项目里悟到的通用经验真正好用的AI工具不是把所有智能都丢给模型而是把模型放到一个精心设计的流程里去发挥能力。就像管理一个优秀但需要明确分工的团队一样你把每个人的职责边界和协作方式定义清楚他们的产出才会稳定可靠。Deep Research Assistant这种“规划—检索—综合—审校”的流水线式设计不局限于调研任务换成竞品分析、文章素材收集、产品需求拆解同样适用。你可以直接复制这套思路然后替换具体模块的提示词和业务规则就能快速搭出一个属于自己的垂直场景Agent。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门