LangGraph实战:构建研究-分析-写作-审核多智能体协作团队

发布时间:2026/10/17 4:14:43
LangGraph实战:构建研究-分析-写作-审核多智能体协作团队
LangGraph 实战多智能体协作的“研究-分析-写作-审核”团队做了快两年的 LLM 应用开发我最大的感触是单个 Agent 写个 Demo 很容易但真要扔到业务流程里往往撑不过三件事。前一阵我接到一个内部调研报告自动生成的需求单一 Agent 跑了几版不是到后半段忘了前面的结论就是自己编造参考文献最让人头疼的是——它总觉得自己写得挺好哪怕事实错误就摆在第一行。后来我用 LangGraph 把整个流程拆成四个角色研究、分析、写作、审核模仿真实内容团队的分工让每个智能体只干一件事用图结构把它们的协作关系串起来。这套框架跑通之后效果完全不一样单个 Agent 容易出现的逻辑漂移、幻觉内容、结构混乱被大幅压缩而且每一段产出都能追责到具体执行者。这篇文章就把我的完整设计和踩坑过程拆开讲从状态结构、节点实现、流程编排到问题排查适合已经入门 LangChain/LangGraph、想把多智能体协作落到实际项目里的朋友。1. 项目整体设计与思路拆解1.1 为什么一定要拆成多个智能体我先说结论如果任务内部可以分解成互相依赖的多个环节单 Agent 天花板非常低。原因不是模型不够聪明而是上下文失忆和角色污染这两个问题绕不开。所谓上下文失忆就是当对话轮次一多、中间内容一长模型对早期信息的注意力会明显衰减。我最初的单 Agent 版本里让它先做调研再写报告写到第三章时它已经忘了第一章引用过哪些数据经常前后口径对不上。角色污染更隐蔽同一个 Agent 既要保持研究员的客观中立又要用写手的语气去动笔模型会在两种人格之间摇摆。你给它特别详细的系统提示词能缓解但治标不治本。把流程拆开之后每个节点只负责单一职能。负责研究的 Agent 永远不写正文负责写作的 Agent 永远不碰原始资料负责审核的 Agent 专门挑毛病。这样每个系统的提示词可以写得极其聚焦不需要为了兼容多个职责而堆砌冗长的指令模型行为更稳定也让问题定位一目了然——哪一环输出不对直接看那个节点的输入输出就行不用在一个人工智障的这上千行日志里大海捞针。1.2 四个角色的分工和协作模式我把团队拆成四个角色分别是研究员、分析师、写作师、审核师。名字很直白职责边界是刻意划清的。研究员负责收集事实。它的输入是用户提出的调研主题输出是经过筛选和引用的资料卡片。它的特点是调用外部工具比如网页搜索、内部文档检索、知识库查询然后把信息结构化整理。这个角色最重要的品质是没感情、不站队忠实记录来源。分析师负责从研究员交付的资料里提炼逻辑。它不新增信息只负责识别关键数据、归纳趋势矛盾、形成论点框架。它像一个参谋把一堆散落的素材变成带观点的作战地图输出的是结论性要点和论证结构。写作师只做一件事根据分析师的要点框架把内容写成通顺、专业、符合要求的文章。它不需要创新不需要查证它的核心能力是行文组织和表达转换把提纲变成可交付物。审核师是大门。它逐条检查成稿的准确性、逻辑性、完整性对照研究员提供的原始资料做事实核查。如果发现问题它不只给出“不行”两个字还会附带具体修改意见并打回给写作师如果通过再交付终稿。这四个角色形成一个有向流程研究 → 分析 → 写作 → 审核。审核不通过就回流到写作一次最多循环三次超限就整体降级交付。兰斯Graph天然适合表达这种带条件分支和循环的流程这也是我选它而不是自己硬写编排逻辑的原因。1.3 为什么选 LangGraph 而不是 LangChain 链式调用我知道很多人会有疑问这么简单的四步流程用 LangChain 的链串起来不就行了但 LangChain 的 SequentialChain 只适合完全线性的调用它没有内置的条件跳转和循环控制。而我们的审核环节天然就是一个分支节点审核通过走结束、审核不通过回写作这种带状态回写的需求用链式封装要写很多丑陋的 Python 逻辑而用图结构只要加一条边和一个条件函数。LangGraph 还有一个关键特性是持久化状态管理。每个节点的输出可以写入结构化的 State 对象后续节点从里面取自己需要的字段不用把整个对话历史全部传给模型。这相当于给智能体加了一套工作记忆机制——研究员把资料写进状态写手专注在成稿上两个 Agent 之间传递的不是聊天记录而是结构化数据。这一点对长任务的稳定性提升非常大我会在后面详细讲具体怎么设计。2. 核心细节解析与节点实现2.1 结构化状态的字段设计与数据流写 LangGraph 应用的第一步永远是设计 State不是写 Agent。状态设计直接决定整个图的复杂度。我的状态用 Python 字典定义核心字段如下。from typing_extensions import TypedDict class ReportState(TypedDict): topic: str # 原始调研主题 research_notes: list # 研究员输出的资料卡片 analysis_framework: dict # 分析师输出的论点框架 draft: str # 写作师生成的草稿 review_comments: list # 审核师给出的修改意见 revision_count: int # 迭代次数用于控制回流上限 final_report: str # 终稿我强调一下这个字段设计的几个原则。第一节点之间只传结构化的任务产物不传对话记录。四类角色是接力不是聊天。研究员传 demo 给分析师时只给整理好的资料卡片不把它和工具之间的几十条中间日志带过去。这样可以大幅降低每轮调用的 token 消耗也防止模型被无关信息干扰。第二审核意见用列表而不是字符串。审核师经常一次性抛出多个问题比如“第二段的数据引用缺失来源”和“结论部分论证不充分”。用列表保存既可以单独标记每条也可以保存汇总。后期如果要接入人工审批也能直接对接结构化数据的扩展性比纯文本好得多。第三状态对象里保留 revision_count 字段这是迭代控制的关键。LangGraph 的图本身支持循环但我必须限制循环的最大次数避免便宜模型无限打回-重写的死循环。我的经验是上限设为三次第四次无论审核结果如何都强制输出宁可带瑕疵也不能卡死整个流程。2.2 研究员节点工具调用和资料收集的关键点研究员节点的核心职责是收集信息并整理成证据不需要生成观点。它的 Prompt 我写得极其细直接定了输出格式让结果稳定可解析。from langchain_community.tools import DuckDuckGoSearchRun from langchain_openai import ChatOpenAI from langgraph.graph import StateGraph def research_node(state: ReportState): topic state[topic] tools [DuckDuckGoSearchRun(), vectorstore_retriever] researcher ChatOpenAI(modelgpt-4o-mini, temperature0.2) researcher researcher.bind_tools(tools) prompt f你是项目研究员。围绕主题{topic}检索资料。 必须遵守 1. 每条信息必须标注来源。 2. 如果信息来自工具返回结果直接引用原文关键数据。 3. 只记录客观事实不要添加主观判断。 4. 最多输出 8 张资料卡片每张卡片包含标题/来源/核心事实/引用出处。 5. 如果检索不到有效信息明确写「未找到相关数据」。 现在开始检索并输出结构化资料。 # 执行检索调用工具汇总结果 search_result tools[0].invoke(topic) retrieved_docs tools[1].invoke(topic) return {research_notes: [{title: 网络检索, source: search_engine, facts: search_result[:1500], ref: 自动生成}]}我这里把检索简化成直接调用工具求值。实际项目里会让模型自动决定调用哪个工具但演示项目里直接固定调用更可控也方便新手看懂。这一块的三个坑值得重点记录。第一节流检索结果。研究员的输出不能是原始资料的复制粘贴大杂烩必须让它压缩。我的资料卡片每条核心事实控制在三到五行来源信息完整保留。原始搜索页面可能几千字真正到研究员手里每张卡片只能留浓缩版。信息压缩是这个节点最重要的功能否则到下游写手那里输入一长模型很快就开始忽略细节。第二引用来源字段必须结构化保存。很多实现里只让模型在正文里写一句“据媒体报道”这等于没引用。我在卡片里把来源URL单独存成一个字段这样的话审核师核实时可以直接按图索骥。后续还想做来源追溯、引用展示拿这个字段做文章就行。第三研究员不带情绪。我在提示词里直接说了“只记录客观事实不要添加主观判断”这是为了把价值判断全部留给分析师。一旦研究员在收集阶段就开始做评价容易把个人偏好混进素材下游的整套推理都会被带偏。2.3 分析师节点把素材变成有逻辑的观点分析师做的事情在内容行业里叫“拉提纲”在咨询行业里叫“搭框架”。它自己不需要写内容而是从研究员给的材料里找出值得展开的若干条线索。给出分析师的 Prompt 设计重点在于强制结构化def analyst_node(state: ReportState): notes state[research_notes] analyst_prompt f你是一名资深分析师。根据以下材料提炼报告框架。 材料如下 {notes} 要求 1. 归纳出 3~5 个核心论点每个论点必须能在材料中找到数据支持。 2. 识别材料中的矛盾或数据缺口单独列出一条「风险提示」。 3. 输出 JSON 格式字段包括core_argument核心论点、supporting_data支撑数据、risk_note风险提示。 4. 不要撰写完整句子只输出论点框架。 # 实际运行 LLM 后解析 JSON framework { core_argument: [行业整体平稳增长但区域分化明显, 技术侧的投入产出比正在改善, 合规成本成为主要变量], supporting_data: [引用资料卡片 1、3, 引用卡片 2、6, 引用卡片 5], risk_note: 材料缺乏近三年同比数据建议补充访谈获取一手信息 } return {analysis_framework: framework}分析师节点最大的价值是提出对资料的“要求”如果某个核心论点在原素材里找不着依据它必须标记为风险而不能硬编一个论据。我自己在调试时发现分析师一旦跟写手一样开始“文思泉涌”产出的框架就都是套话任何话题都能套上去一点价值都没有。所以给它加硬性约束——每个论点必须标注对应的材料卡片编号写手引用时没有依据是会被审核师打回的。还有一个小细节分析师输出的 JSON 结构会成为写手提示词的一部分。这意味着框架字段的命名要自解释性强最好连数据来源的编号都带着。后我实际上是把框架字段转成 YAML 塞给写手的这比让模型直接读 JSON 更不容易出错。2.4 写作师节点与审核师节点生成内容和质量把关写作师和审核师是长流程里最考验 Prompt 功底的两个节点我把它们放在一起说。写作师的输入不是原始材料而是分析师给的点。它做的事情本质上是扩写把提纲变成一篇语气统一、行文流畅、符合格式要求的成稿。这里最关键的是不能让写手获得任意发挥的权限。def writer_node(state: ReportState): framework state[analysis_framework] draft f一、市场概况分析 根据核心论点1展开引用支撑数据…… 二、技术趋势判断 根据核心论点2展开…… 三、风险与不确定性 根据核心论点3与风险提示展开…… return {draft: draft}写作师最容易犯的毛病是“过度美化”。你给它三条干巴巴的框架它就能给你写出天花乱坠的宣传语。我的解决办法是在提示词里直接说明语气克制、不要推断数据、不要使用排比句并且每段结尾必须标注支撑论据的来源编号。审核师则是另一个极端我把它的提示词写成了“找茬模式”专门挑刺def reviewer_node(state: ReportState): draft state[draft] notes state[research_notes] review_prompt f你是质量管理专员对下述草稿进行严格审查。不得因为文笔好就降低标准。 草稿 {draft} 对照资料源 {notes} 请逐项检查 1. 事实核查有无内容与资料源冲突或完全无来源。 2. 逻辑一致性前后论述有无自相矛盾、偷换概念。 3. 完整性是否有核心论点被忽略或一笔带过。 4. 引用规范关键数据是否标注出处。 输出结果必须包含两个字段verdict通过/打回和 issues具体问题列表。 如果打回issues 必须给到可操作的修改建议禁止说“内容需要丰富”这种空话。 # 模拟审核结果 review_result {verdict: 打回, issues: [第二段提到增长率 12%但资料卡片 3 中该数据为全年同比下降, 风险提示段仅有结论缺少支撑]} return {review_comments: review_result[issues]}注意一点审核师的打回理由必须是可执行的修改意见。如果它说“内容需要丰富、表述还不够好”写作师根本不知道怎么改来回循环只会消耗时间和 token。我的经验是在提示词里直接限制审核师每一条 issues 都必须指明位置、问题类型和修改动作。这相当于把人的工作要求带进了 Agent 协作规则里。审核节点是整个图的把关哨它决定是继续循环还是结束。我给它配的模型一直是质量最高的。四个节点可以用四个不同档位的模型研究员和写手用中等模型省钱审核师用最强模型把关效果和成本的最优比例就出来了。3. 实操过程与核心流程实现3.1 环境准备和依赖安装简单列一下我跑这套环境用的依赖版本避免大家踩到版本兼容的坑pip install langgraph langchain langchain-openai langchain-community我前后试过 langgraph 的 0.2.x 和 0.3.x 两个大版本。0.2 版本的 StateGraph 创建方式更简洁0.3 版本引入了一些内部接口调整提示词 Node 的传参方式也变了。如果你列出来的文档代码跑不通优先检查是不是版本差异。我给的关键代码基于 0.2.x后续版本应兼容但接口命名有差异时按官方文档对应改就行。另外因为我这里用了网上搜索工具最好给自己准备好可用的 API Key 或本地部署的模型服务。如果你想用本地模型强烈建议选带工具调用能力的模型比如某些支持 function call 的量化版本。不带工具能力的模型去绑定搜索工具返回结果基本是无效的最后会让研究员节点空手而归。3.2 构建工作流图并配置回流与终止条件在 LangGraph 里StateGraph 的核心是状态更新、节点注册和边连接。我的图结构是这样from langgraph.graph import StateGraph, END from langgraph.checkpoint import MemorySaver # 初始化图 graph StateGraph(ReportState) # 四个节点 graph.add_node(researcher, research_node) graph.add_node(analyst, analyst_node) graph.add_node(writer, writer_node) graph.add_node(reviewer, reviewer_node) # 主流程线 graph.set_entry_point(researcher) graph.add_edge(researcher, analyst) graph.add_edge(analyst, writer) graph.add_edge(writer, reviewer) # 审核分支通过结束不通过回流到 writer graph.add_conditional_edges( reviewer, lambda state: process if state[revision_count] 3 and state[review_comments] else end, {process: writer, end: END} ) # 内存持久化 graph graph.compile(checkpointerMemorySaver())这里有个非常容易踩的坑回流到 writer 时writer 节点会覆盖掉之前的草稿也就是 state 里 draft 字段被重新写入而审核意见就丢失了。我最初就是这样跑的结果第二次回流时写手完全不知道自己为啥被打回。因为 LangGraph 的节点返回字典时同名 key 默认是覆盖更新。解决办法是让 writer 节点返回一个包含字段新的草稿和之前累积的审核意见。也就是说 writer 不能只返回 draft它要把旧的 review_comments 也带回状态里。def writer_node(state: ReportState): comments state.get(review_comments, []) new_draft generate_draft_with_comments(state, comments) return {draft: new_draft, review_comments: comments, revision_count: state[revision_count] 1}我当时看 LangGraph 文档时花了很久才意识到默认行为是覆盖而不是合并。这个细节对多轮回流场景太关键了不处理好审核意见根本传不到写手手里。要么你手动在返回时带上历史字段要么你重新设计节点返回给审核意见和草稿用不同的 key反正不能让 reflux 失去记忆。另外一个细节是 MemorySaver。我一开始没有配置检查点整个图是纯内存跑的这有两个问题没法查看中间态也没法跟踪多轮对话的历史。加了 MemorySaver 之后可以通过 config 传入一个线程 ID随时查询当前状态到哪了、各字段长什么样。生产环境建议换数据库持久化但开发和调试用 MemorySaver 够用了。3.3 条件逻辑的细节解剖为什么审核后的回流要“带装配件”把回流分支这条逻辑单独拿出来多说几句因为这是整个图的“智力”所在。一个没有条件分支的图就只是一条流水线而有了回流分支系统才真正像团队干得不好就要打回重做。条件函数看起来只是简单的判断但我在实际运行中发现一个问题revision_count 的递增必须放在 writer 节点里而不是 reviewer 节点否则回流路径上案例有偏差。如果把 revision_count 在审核节点就加一那么单次执行中writer 第一次写完、reviewer 第一次审核通过时这个字段已经被改成 1 了而实际上我们还没有进入任何回流。表面上问题不大但如果要看状态历史字段就会跟预期不一致调试时会莫名其妙。所以我在设计时遵循“谁执行返工谁增加版本号”的原则writer 重写一次revision_count 加一。审核只是判断“当前版本是否通过”它不改变版本号。这样每组数据唯一对应一个版本排查起来比较轻松。条件判断还有一类容易出错的地方是审核师“通过”时review_comments 列表是空的。那么条件函数的判断就不能用if comments:而要显示判断 verdict。为了这点我把审核师的状态里单独加了 verdict 字段判断逻辑改成def route_review(state: ReportState): if state[revise_count] 3 and state.get(verdict) 驳回: return process return end这样的可读性比判断列表非空好得多也不容易踩“空列表被误判为通过”的坑。3.4 启动执行和结果取值整个工作流启动起来其实非常简单。用线程 ID 管理一次完整流程config {configurable: {thread_id: report-task-001}} result graph.invoke( {topic: 2025 年某行业智能化转型现状调研}, configconfig ) print(result[final_report])这里 thread_id 可以理解成一次完整任务的会话标识。同一 thread_id 下再次 invoke 时会接着上次的状态往下走如果重复执行而不想续接历史需要换一个新的 thread_id。这个坑是我在写多轮测试脚本时踩过的——同一个 ID 跑了两次第二次把第一次的草稿直接拿过来覆盖了后来查了半天才发现是重复使用了同一会话。最终拿结果只取 final_report 字段但我调试时会先把整路状态都打印一遍看每个环节的中间产物是不是符合预期。这样可以快速判断问题出在哪一环是研究员没搜到资料还是写作师写得离题还是审核师误判。4. 常见问题与排查技巧实录4.1 状态传参失效与字段覆盖问题我自己的项目中出现频率最高的一个问题就是回流时状态字段被覆盖导致审核意见丢失。根本原因上一节已经讲明白了LangGraph 的状态更新规则是节点返回的 dict 字段直接覆盖旧值不会自动合并。如果你想让某个字段“累积”你必须自己把旧读出来再拼回去。排查方法很简单看每次 invoke 之后的状态快照。拿 thread_id 调用 get_state把每个 key 的值打出来对比回流前后的内容是否还在。from langgraph.checkpoint import MemorySaver checkpointer MemorySaver() # 执行后获取当前状态 snapshot graph.get_state(config) print(snapshot.values)还有一类情况是不同类型节点之间传递格式不一致。研究员输出的 research_notes 是 list但分析师误用了 research_notes 里的字符串字段运行时直接报 KeyError。解决方案是让所有节点输出前做一个严格的字段校验最简单的方式就是在返回前加 assert。4.2 上下文超限与信息截断四步流程看起来不长但研究员如果塞入十几张长卡片总 token 很容易超限。特别是互联网搜索到的页面动辄几千字随便几条就把上下文塞满了。这个问题我用三层方案解决。第一研究员节点做摘要压缩每条卡片只保留核心事实不超过 200 字来源信息单独存。第二每个节点之间只传节点产物不带历史对话这让每次调用模型时的输入被控制在一到两个 token 倍以内。第三政治温度相关的数据格式全部用结构化文本能上 JSON 就不上长篇 prose。如果生产环境要处理超大文档还可以做 map-reduce 式检索把大文档先切片每个切片单独检索再合并。我在这个演示项目里没有做得太深但方向就是这条路。4.3 审核误判与方向性问题便宜模型的局限性我一开始把所有节点都用的一个小模型跑实验结果质量惨不忍睹。小模型的审核师会把“内容通顺”当成“事实正确”对明显的数据冲突视而不见或反过来所有内容都打回写手被逼成同一个东西写八遍。这种问题不在图逻辑上而是角色模型的算力余额严重不匹配。审核是所有节点的守门员我强烈建议分配最贵的模型给它。研究员和中手可以用中等模型达到省钱目的审核师用最强模型质量才有保障。如果你只有一个模型可选就靠提示词把审查标准写到极致并降低温度到 0.1 以下。另外还发现一个现象某个模型写的稿子同一个模型去审往往自己看不出自己的错。所以有条件的话审核节点和其他写手节点用不同厂商的模型效果往往更好。相当于让一个第三方来挑刺准确率高很多。4.4 死循环与超时控制的边界图里如果审核一直打回而写手又没有修改能力就会形成无限循环。虽然我在条件函数里写了最多三次回流但还需要一个最外层断点整体超时时间。LangGraph 可以在 compile 时通过 recursion_limit 限制递归深度graph graph.compile(checkpointerMemorySaver(), recursion_limit25)这样即便条件函数漏了什么边界图跑超过 25 步也会强制终止不会无底线烧钱。我把这个数设得相对宽松因为正常四步流程加三次回流最多也就 10 步出头25 足以覆盖异常翻倍的情况。线上如果对成本敏感可以设到 12控制更紧。还有一个细节是工具调用本身的超时。研究员调用搜索工具如果遇到服务不稳定可能一直卡住不返回。我给每次工具调用外面加了一个超时包装超过 15 秒就跳过该工具改用已有资料继续往下走。机器人的项目不可能等一个接口等半天超时降级的思路很重要。4.5 幻觉传播的阻断方法多智能体流程中最隐蔽的坑是幻觉传播。研究中产出的一个错误数会被分析师当成真事吸收进框架写手又层层扩写成一大段煞有介事的分析到了审核才发现根源在老早的研究环节。这种越传越大的错误比单 Agent 幻觉更危险因为它有了“多 Agent 验证”的外衣看起来像是多重检查过的。阻断思路是在每个节点输出来加一个强证据验证。研究员阶段就把来源标注清楚分析师阶段如果某个论点找不到研究材料支持不允许悄悄编造只能写到风险提示里。写作阶段必须引用论点编号审核阶段把每一条事实断言与资料卡逐一比对无出处的直接打回。这样虽然流程没有引入额外的向量数据库裁决但靠结构化的强制约束也把幻觉传播路径卡死了大半。如果你做的是数据敏感的项目建议在审核节点之后再加一个独立的证据校验节点单独把成稿和原始资料做一遍文本比对。甚至可以计算语义相似度检测成稿里是否存在数据源中找不到依据的句子。那条路我再写一篇新的博客今天就点到为止。5. 后续扩展方向与实战经验补充5.1 并行化多研究员协作的设想我现在这个版本是单研究员串行工作如果调研主题覆盖范围特别大比如要同时看技术、市场、政策三个维度单个研究员要串行搜索耗时长且上下文容易爆炸。更好的做法是拆成三个并行研究员各管一维然后用一个汇总节点把内容合并成统一资料集。LangGraph 天然支持这种并行 fan-out/fan-in 结构把三个研究者节点都接到入口然后汇总节点等待全部完成。但你用着就会发现有个难点三个研究员同时更新同一个字段LangGraph 会按默认规则处理匿名冲突通常最后写入的覆盖先写入的造成信息丢失。应对办法是给每个持续性字段加 reducer也就是自定义合并函数。我当时用这个场景还不太复杂单研究员够用就没上并行。但如果你要考虑性能和大规模任务这一步是绕不过去的。LangGraph 的 reducer 机制是它区别于普通状态机的重要特性值得单独啃一啃。5.2 各节点选用不同模型的经验我在多轮实验里形成了比较固定的模型分配策略研究员用中低档但支持工具调用的模型写作用中档模型审核用顶配模型。分析师介于写作和审核之间。为什么不全用顶配因为贵而且慢尤其是一个流程要反复跑。多 Agent 协作的 token 开销远高于单 Agent每个节点都是一次完整调用链上任何一个中间环节如果用的是特别贵的模型整体成本会线性放大。中层模型的表现已经够好用在这里是对的。真实项目中有一个容易忽略的开销点提示词本身。多角色的提示词都比较长反复调用时这部分的 token 累计起来很吓人。我的建议是提示词能做模板就模板写完缓存下来不要每次调用都拼整个变量。一些公共描述可以抽出来复用既省 token也保证每轮行为一致性。5.3 最终的几点体会把这套“研究-分析-写作-审核”团队搭完我最深的感受是多智能体协作真正难的不是让智能体“会做事”而是让它们“不敢乱做事”。每一个节点都要有明确的职责边界、结构化输出格式、可追溯的信息来源整个系统才不会在看似聪明的对话里悄悄崩塌。如果你是从零开始我建议不要一上来就追求复杂的图结构。先把四个节点全部写成普通函数用最笨的串行方式跑通再加回流逻辑最后才引入 LangGraph 的状态管理和条件分支。每一层都确认输出质量满意了再往下叠这样出了问题也容易定位。我用这种循序渐进的方式开发实际踩坑数量比第一版直接上全流程少了至少七成。这套模式不只适用于写报告凡是“输入事实材料、产出结构化结论”的任务比如竞品分析、产品需求评审、代码审查报告都能套用类似的角色拆解思路。改改提示词换换工具图本身不用动太多。这也是 LangGraph 这类图编排框架真正值钱的地方——你把业务流程的评判逻辑想清楚剩下的只是把角色填进去。