1. 项目概述为什么要用 LangGraph 做写作 Agent先说一个我自己的真实感受用单一提示词让大模型写稿第一版往往能用但离“能直接发”的距离还很远。要么结构松散要么事实经不起推敲要么读起来一股“AI味”。你反复喂提示词它改了这处又坏了那处整个体验像是在跟一个记性很差的人合作。真正的问题是写作本身是一个多阶段的认知过程——先想清楚写什么、再组织素材、然后落笔成稿、最后还得自审改进——它不是一次生成能完成的。LangGraph 解决的就是这件事。它把大模型的调用从“单次文本进、文本出”变成一个有状态、可编排、能循环的工作流。你可以把写作过程拆成规划、建模、生成、自审、修改等节点让所有节点共享同一个状态池让流程自己决定是继续改稿还是输出终稿。听起来很玄乎实际上就像是在给 AI 装了一套“写作 SOP”。在我实测过的方案里用 LangGraph 编排的写作 Agent 比单次提示词方案在长文一致性和修改可控性上都要好得多。这篇文章适合两类人一类是被“提示词写稿”折磨过、想体验 Agent 工作流的开发者另一类是刚开始接触 LangGraph、想找一个有完整落地价值的实战项目来练手的同学。我会把项目拆开揉碎从架构设计、节点实现到问题排查全部过一遍最后附上可以扩展的方向。代码里涉及的模型 API 我用的是兼容接口你可以平滑替换成自己的配置。从项目搭建的整体节奏来看我们大概会经历这样几步确定 Agent 的职责边界 → 设计工作流状态 → 实现各个功能节点 → 组装成图 → 跑通一次完整的自动化写作流程。其中最容易踩坑的是状态定义和节点返回值不匹配这个我后面会用一整节专门讲。2. 架构设计从单次调用到图编排的思路转变2.1 写作流程到底应该拆成几个阶段如果你仔细回想一下自己是怎么写一篇长文的大致会经历“确定方向 → 收集信息 → 列大纲 → 写初稿 → 重读修改 → 定稿”这么几个环节。AI 写作 Agent 也一样但在图里我们需要为每个环节建一个节点并明确它们之间怎么流转。我最初设计的时候把节点压缩成了三块规划节点、生成节点、自审节点。听起来很简单跑起来发现太粗糙——规划节点输出的结构经常被生成节点无视自审节点又容易因为标准模糊而空转。后来我参考了真实编辑部的工作方式把流程改成了五个环节先由一个策划节点产出主题方案再由素材节点生成写作大纲和要点清单接着是初稿节点负责把大纲铺成文字然后是评审节点从逻辑、事实、表达三个维度打分并提出修改建议最后是修订节点接受建议并产出新版本。这样拆分的好处是每个节点只负责一件事输出结构清晰后续要替换或者升级任何一个环节都不用动其他代码。在实际操作里这五个节点不是必须全部从头实现。LangGraph 的优势是编排能力节点内部依然可以调用模型完成具体任务。比如“初稿节点”内部我可以让模型先输出段落再逐段润色这本质上是用一个大模型完成多个子任务。关键是图的边界要卡在“任务类型”上而不是“模型调用次数”上。一旦你按后者切分图会变得又碎又难维护。2.2 状态池让每个节点都能读到同一份上下文LangGraph 里的状态State就是整个工作流的共享内存写稿过程中所有信息——主题、大纲、当前稿子、上次的评审意见——都放在这个字典里。节点函数接收状态作为参数处理后返回一个字段更新的字典框架负责把新值合并回状态池。这种设计特别适合写作场景原因很简单写作的上下文是累积的每个环节都需要看前面环节的输出。我定义状态用的是 TypedDict包含这样几个字段topic 是核心主题outline 存大纲列表draft 存当前稿件全文review 存最近一次评审意见iteration 记录修订轮次status 标记当前处于哪个阶段。可能你会觉得字段有点多但实际跑起来这些一个都不能少。尤其是 iteration 这个字段它是防止图“陷入死循环改稿”的关键我后面讲自审节点时会专门说为什么。这里有个非常容易犯的错节点函数的返回值必须是一个字典键名要和状态字段完全一致。写错了虽然是小事但排查起来很费时间因为报错不一定指向键名而是会显示奇怪的类型不匹配。我当时的做法是给每个节点的返回值单独加一层校验确保只返回状态里定义过的键后面省了很多事。2.3 边的三种走向流程控制的核心图编排最核心的就是边。LangGraph 支持普通边、条件边和循环边这三种边在写作 Agent 里都能找到对应的使用场景。普通边最简单就是按顺序执行节点比如素材节点之后一定跟初稿节点条件边则需要一个函数来判断走哪条路比如评审节点跑完如果修订轮次超过上限就直接走输出节点否则走修订节点循环边则是把“初稿 → 评审 → 修订 → 再评审”这个路径串起来。理解边的最好方式是把图当成一张流程图。节点是流程框边是箭头条件边就是每个菱形判断框。LangGraph 只是把这个流程图变成了可执行代码而且每次运行的状态都会被记录下来方便复盘。在编排时我推荐你先画一遍流程图再写代码。我一开始直接上手写代码反复调整边的关系改了好几版。后来在纸上画了一遍五节点的流转关系五分钟就理清了策划→素材是普通边素材→初稿是普通边初稿→评审是普通边评审→修订是条件边修订→评审是循环边评审→输出也是条件边判断条件是修订轮次是否达到上限。3. 核心节点详解与关键参数解析3.1 策划与素材节点定方向、搭骨架策划节点干的事是接收用户给的主题词产出一份《内容策划方案》。这个方案里包含目标读者、文章类型、核心观点、预期篇幅和大致结构。为了让方案足够可执行我会在提示词里明确要求模型用 JSON 格式输出字段名固定为 title、audience、type、core_view、structure每个字段的含义都有说明。很多人觉得策划节点不就是一次提示词调用嘛没什么技术含量。但实际跑下来我发现它的输出质量直接决定整篇稿件的上限。提示词如果太笼统模型给出的方案就会泛泛而谈。我在写提示词时做了三处约束给出具体的写作场景背景比如“面向刚接触自动化办公的职场人”强制要求核心观点必须体现态度不能只说“本文介绍了某某”结构至少包含引入、论证、案例、总结四个部分。素材节点紧接着策划节点负责把策划方案扩展成写作大纲。这个节点需要明确列出每部分的要点、支撑素材类型和大致字数。我要求模型输出包含三级标题的 markdown 格式大纲每个标题下配一句内容说明。这里有个实操细节为了让初稿节点能“读懂”大纲素材节点的输出尽量结构化成列表而不是一大段文字否则初稿节点解析起来会很崩溃。两个节点的模型参数也有讲究。策划节点我通常把 temperature 调到 0.7留一点发挥空间素材节点则调到 0.3追求稳定输出。同一个模型在不同节点用不同参数在 LangGraph 里完全是常规操作因为你每个节点的函数可以独立构造模型调用。3.2 初稿生成节点把大纲铺成一篇完整稿子初稿节点是工作量最大的环节。它的任务是接收大纲生成一篇结构完整、逻辑通顺、细节充实的稿件。我做过两版实现第一版是一整篇让模型生成速度很慢长文还经常出现前后不一致的情况比如前面说“五个要点”后面只写了三个。第二版改成逐段生成——我写了一个循环遍历大纲里的每个一章标题让模型每次只生成那一章的内容然后拼接起来。逐段生成的好处非常明显一是单次生成的内容短输出质量和上下文稳定性都高很多二是每章生成的 prompt 可以带着上一章的结尾避免章节之间出现明显的断层三是某一章生成失败时可以单独重跑这一章不用整个初稿重来。坏处也有就是总耗时会变长因为每次调用模型都有固定的首字延迟。我的处理方式是生成时直接在后台打印进度让你知道正在写哪一章。这一节里我强烈建议你在初稿节点的提示词中明确“保留大纲中所有要点不得增删”。不然模型经常自由发挥给你加几个大纲里没提过的段落回头评审节点拿初稿跟大纲对比时就会判定“结构不匹配”导致不必要的修订循环。初稿生成时另一件重要的事是设定最大输出长度。大多数模型接口支持 max_tokens 参数如果设置太小稿子会被截断。我一般按一章 800 到 1200 字来算给参数留出充足余量。如果你用的是流式输出还要额外做拼接处理LangGraph 节点函数里可以直接调用流式接口把 token 拼完再返回。3.3 评审与修订节点让 Agent 自己挑毛病改稿子评审节点是整个 Agent 最像“人”的地方。它拿到的输入是当前稿子和修订轮次输出是一份结构化评审意见包括逻辑问题列表、事实疑点列表、表达优化建议列表以及一个 overall_score 分数1 到 10。要求它必须输出 JSON是因为我后续的修改提示词要精准引用这些字段如果把评审意见写成一大段自然语言修改节点用起来就很不顺手。最初版本的评审节点会陷入一个低水平循环模型反复说“文章可以进一步润色”修改节点就反复重写但重写后的稿件变化极小。后来我想了一个招在提示词里要求“评审者必须引用原文中具体的句子来支撑每条意见”并且要求每条意见必须标注严重级别高、中、低。这样做之后评审意见的含金量高了非常多修改节点也终于知道该改哪里了。修订节点的工作是拿到评审意见后逐条决定是否接受然后重写稿件。为了让重写更可控我是把原稿分段传入让模型在每段下面输出修订版本而不是让模型重写整篇。这样一是输出更稳定二是我可以轻松对比原稿和修订稿反过来验证评审意见是否被落实。修订节点同样受 iteration 字段约束当轮次达到 3 轮时即使评审分数不高图也会强制走向输出节点避免无限循环烧 token。这里插一个印象最深的参数调整最初我让评审节点直接输出“通过/不通过”图就据此决定是否输出终稿。结果模型偏向保守动不动就“不通过”最后都是靠硬性轮次上限才结束的。后来我把判断逻辑改成分数制评分高于 7 分就直接输出低于 7 分加上轮次未满才允许走修订。改完之后整个流程的收敛速度快了不少写稿体验也从“无限拉扯”变成了“最多改两遍”。4. 从零搭建完整实操过程与代码实现4.1 环境准备与状态定义这个项目我用的 Python 3.10 环境LangGraph 版本选的是 0.1.x 系列模型调用走的是兼容 OpenAI SDK 格式的 API。先把依赖装齐pip install langgraph langchain-core openai python-dotenv如果你是刚接触 LangGraph安装时建议直接装最新版但要注意 API 细节可能有变化。我的示例代码跑在 0.1 版本上如果你用的是更新的版本个别类名或者函数签名需要按官方文档微调。接下来定义状态结构。我用 TypedDict 来约束状态字段这样每个节点的输入输出都有类型提示对排查问题非常有帮助from typing import TypedDict, List class WritingState(TypedDict): topic: str plan: dict # 策划方案 outline: str # markdown大纲 draft: str # 当前稿件全文 review: dict # 最新评审意见 iteration: int # 修订轮次 finished: bool # 是否终稿我在项目里还加了一个辅助函数专门用来读取和打印状态方便观察每轮节点运行结果。别小看这个函数调试复杂 Agent 时它能帮你厘清状态在每个阶段的真实形态而不用靠脑补。4.2 实现四个核心节点函数节点函数本质上就是一个普通的 Python 函数输入是当前状态输出是状态更新字典。我把四个核心节点都贴出来方便你对照理解def plan_node(state: WritingState) - dict: 策划节点根据主题生成策划方案 prompt f 你是资深内容策划编辑。针对主题“{state[topic]}”制定一份内容策划方案。 必须输出JSON格式字段如下 - title: 文章标题 - audience: 目标读者 - type: 文章类型 - core_view: 核心观点要体现明确态度 - structure: 文章结构列表至少包含引入、论证、案例、总结四个部分 要求核心观点不能是空泛的“介绍”“探讨”必须是一个可讨论的判断句。 resp call_model(prompt, temperature0.7) # 返回字符串 plan parse_json(resp) return {plan: plan}这里 call_model 是我封装的一个兼容函数它负责把消息发给模型接口并返回文本。parse_json 则是从模型回复里提取 JSON 的辅助函数内部做了容错处理——有些模型会在 JSON 外面套一层 markdown 代码块我提前用正则清理掉避免解析失败。素材节点的实现类似但把策划方案直接嵌进提示词里要求输出大纲def outline_node(state: WritingState) - dict: 素材节点根据策划方案生成写作大纲 plan state[plan] prompt f 根据以下策划方案生成一篇干货类文章的大纲。 策划方案{plan} 大纲要求 - 使用markdown格式以“## ”表示章节标题 - 每个章节标题下用一句话说明该章节的核心内容 - 大纲必须覆盖方案中的所有结构部分 - 不要写入正文内容 大纲 outline call_model(prompt, temperature0.3) return {outline: outline}初稿节点我采用了“逐章生成”的策略先解析大纲提取所有“## ”标题然后循环调用模型生成每一章def draft_node(state: WritingState) - dict: 初稿节点按大纲逐章生成正文 outline state[outline] chapters parse_outline_chapters(outline) # 返回章节标题列表 full_draft [] for i, chapter in enumerate(chapters): prev_text \n.join(full_draft[-3:]) # 拿最近三段作上下文 prompt f 基于以下大纲章节生成正文 章节{chapter} 前文内容供衔接{prev_text} 要求 - 字数控制在800~1200字 - 逻辑连贯、信息密度高 - 保留所有要点不得遗漏 - 直接输出正文内容不要输出标题以外的多余文字 section_text call_model(prompt, temperature0.5) full_draft.append(f## {chapter}\n\n{section_text}) return {draft: \n\n.join(full_draft)}评审节点和修订节点是一对“拍档”。评审节点输出结构化 JSON 评审意见修订节点根据意见逐段修改def review_node(state: WritingState) - dict: 评审节点多维度评估稿件质量 prompt f 你是一名严格的文字主编。请对下面的文章进行全面评审。 文章内容 {state[draft]} 输出JSON格式评审意见字段如下 - logic_issues: 逻辑问题列表每项须引用原文中的句子 - fact_issues: 事实疑点列表每项须说明疑点原因 - style_suggestions: 表达优化建议列表 - overall_score: 浮点数1到1010分为最高 注意每条意见必须引用原文句子来支撑禁止空泛评价。 评审意见 review parse_json(call_model(prompt, temperature0.2)) return {review: review}def revise_node(state: WritingState) - dict: 修订节点根据评审意见逐段修改稿件 review state[review] issues review.get(logic_issues, []) review.get(style_suggestions, []) if not issues: return {finished: True} prompt f 请根据以下评审意见修改文章段落。 评审意见 {issues} 原文 {state[draft]} 要求 - 只修改与评审意见相关的部分不要无关重写 - 保留原文的整体结构和风格 - 直接输出修改后的全文 修改后的全文 new_draft call_model(prompt, temperature0.4) return {draft: new_draft, iteration: state[iteration] 1}评审节点的提示词里我加了“引用原文句子”的要求这招真的立竿见影。它会逼着模型去读原文而不是凭感觉给一个无关痛痒的宽泛意见。修订节点里如果评审意见列表为空就直接让它结束避免无意义地重写一遍。4.3 组装图把节点穿成完整的写作流水线节点写完之后剩下的就是把它们拼成一张可执行的图。LangGraph 用 StateGraph 来构图我按照前面画的流程把节点和边逐一加进去from langgraph.graph import StateGraph, END def should_revise(state: WritingState) - str: 条件边判断是否进入修订循环 if state[finished]: return end if state[iteration] 3: return end # 评分高于7或者没有评审意见直接输出 review state.get(review, {}) score review.get(overall_score, 0) if score 7 or not review.get(logic_issues): return end return revise graph StateGraph(WritingState) graph.add_node(plan, plan_node) graph.add_node(outline, outline_node) graph.add_node(draft, draft_node) graph.add_node(review, review_node) graph.add_node(revise, revise_node) graph.set_entry_point(plan) graph.add_edge(plan, outline) graph.add_edge(outline, draft) graph.add_edge(draft, review) graph.add_conditional_edges( review, should_revise, {revise: revise, end: END} ) graph.add_edge(revise, review) app graph.compile()编译出来的 app 对象就是可以反复调用的执行器。每次给他一个初始状态它就会按照图的逻辑走完整条链路。我在实际运行中为了看执行细节还加了一行回调from langgraph.graph import GraphRecorder def run_agent(topic: str) - str: initial_state { topic: topic, plan: {}, outline: , draft: , review: {}, iteration: 0, finished: False, } result app.invoke(initial_state) return result[draft]跑一次之后result 状态里所有字段都是最终值你可以直接从中拿到全文和评审意见。4.4 运行效果与耗时观察我拿“自动化办公如何改变职场人的一天”这个主题做了一次测试。整个流程跑了 3 轮修订总耗时约 1 分 40 秒产出约 4200 字。策划节点跑了 3 秒素材节点 4 秒初稿节点最耗时逐章生成花掉了 65 秒。评审节点打分 6.3 分给出的主要意见集中在案例部分不够具体修订节点改完之后再评审升到 7.8 分达到自动输出条件。如果你觉得这个耗时太长最有效的优化路径是给初稿节点加并行能力。LangGraph 支持并行执行节点把各章节生成拆成独立任务一起跑能大幅压缩初稿环节的时间。我当时没做并行一是想保持代码简单二是怕并行情况下上下文衔接不好处理。如果后续有需求可以把章节生成逻辑抽成独立节点让图天然并行。5. 避坑指南我踩过的几个典型问题5.1 JSON 解析失败模型偶尔不按格式输出模型输出不一定是合法 JSON尤其当你用的是开放接口时偶尔返回带 markdown 标记、说明文字甚至截断的 JSON。如果你直接 json.loads十有八九会报错。我的解决办法是写一个容错解析函数先清理 markdown 代码块标记再尝试解析解析失败的情况下用正则从文本里提取大括号范围再解析。如果还失败就干脆返回一个默认评审意见保证流程不中断。这类问题在策划节点和评审节点最容易出现因为这两个节点的输出都是结构化 JSON。素材节点输出的是 markdown 大纲一般不会解析失败但我也碰到过一次模型多输出了一段解释文字把大纲“污染”了。后来我在提示词里明确“不要输出任何解释性文字只输出大纲本身”问题就消失了。5.2 初稿重复和截断上下文太长惹的祸逐段生成虽然稳定但章节一多传入的前文摘要可能放不下全部上下文。我用的是取最近三段作为衔接上下文但有些章节之间需要完全独立的上下文强行只取最近三段会丢失前面的关键信息。后来我调整成了“取最近三段 大纲中当前章节的完整说明”保证模型在生成每一章时都清楚它要写什么内容。截断问题的另一个来源是输出长度参数设置得太小。有一次我把 max_tokens 设成 800生成一章 1000 字的文字结果末尾被硬切成半句话。检查了半天才发现是参数问题。如果你不确定单次生成的长度建议把 max_tokens 设成目标字数的 1.5 倍以上宁可浪费一点也别截断重跑。5.3 评审循环空转分数制代替二值判断前面提到过的评审循环空转问题我再展开讲一下。第一版我让评审节点给出“通过”或“不通过”两个选项结果模型多数时候都选“不通过”即使稿子已经相当不错。后来我又试过让评审节点只给“修改意见”不判断是否达到标准结果导致修订次数极多。最终方案是分数制加轮次上限双保险评分低于 7 分且轮次少于 3 次才能触发修订否则直接进入输出。这套逻辑跑了一个月没有一次陷入死循环。这个教训其实适用于所有带“自审”的 Agent机器的判断和人的判断标准不一样二值判断容易偏保守多值打分加硬性上限才是工程上最省事的方案。5.4 模型返回空结果接口超时和网络波动在线 API 调用不可避免会遇到超时。我在 call_model 函数里加了重试机制单次调用超时 60 秒即重试最多重试 3 次。重试之间间隔指数递增避免加重服务端压力。如果你本地网络不太稳定建议把超时时间适当调大同时在每个节点函数的开头加一行日志看到底是哪个节点在重试。一个隐藏的小坑如果模型 API 返回了内容但全是空白字符代码不会报错但稿子会变成空字符串。我在解析函数里统一做了 strip 检查发现空字符串时抛出一个自定义异常由外层逻辑捕获后重新调用一次模型避免静默失败。5.5 状态字段过期同一个字段被反复覆盖LangGraph 的节点返回值是合并进去的如果你两个节点都返回同一个键名后面的值会直接覆盖前面的值。我碰到过一例修订节点除了返回 draft还顺手返回了 review 字段把评审节点刚生成的评审意见给覆盖了。之后评审节点读取到的 review 变成修改前的旧数据逻辑全乱了。排查了很久最后发现问题出在修订节点的 return 字典里多了一个不该有的键。这也是为什么我建议每个节点的返回字典只包含自己职责范围内的字段。6. 进阶方向与个人心得6.1 给 Agent 加工具联网搜索和实时信息目前这个写作 Agent 的知识来自模型参数写一些专业领域的内容容易出现事实性错误。评审节点最多只能基于模型内部知识判断“疑点”不能真正核对事实。要解决这个问题可以给 Agent 接入联网搜索工具或知识库检索。LangGraph 有专门整合工具的节点类型你只需要把搜索函数包装成节点内部可调用的工具让素材节点在生成大纲前先检索一轮相关资料。我做过一个实验版在素材节点里增加了一次搜索调用让它拿到几条权威资料再写大纲效果非常明显——至少不会在常识性问题上翻车。不过接工具也会引入新的变数搜索结果可能质量参差不齐需要在提示词里明确过滤标准。如果后续打算做垂直行业的知识库问答类 Agent这个方向值得深入研究。6.2 多 Agent 协作与人工审批节点把这张图放到真实的工作流里还可以做“多 Agent 分工”一个专门做采访提纲的 Agent、一个专门做数据整理的 Agent、一个专门做正文的 Agent三者通过共享状态池协作。LangGraph 支持多 Agent 的图编排思路和电磁炉一个道理每个 Agent 是独立的子图主图负责调度它们的调用时机和交接条件。此外真实生产环境下最好加一个人工审批节点放在终稿输出之前。自动评审可以筛掉明显问题但最终发不发还是需要人来确认。这个节点在图上实现非常简单增加一个“等待输入”的边即可。对想落地到团队的场景这一道人工把关比任何自动评分都可靠。6.3 我在这个项目里的三点体会第一Agent 的价值不在于让 AI 单独完成工作而在于把工作过程结构化让每一步都可以被干预、被改进、被复盘。第二提示词依然非常重要但在图编排中它只是“节点内部的事”你需要花更多精力去设计节点之间的输入输出契约。第三做这类项目先在纸上画清楚边的走向再写代码比直接一头扎进代码里效率高得多。说实话写稿 Agent 只是 LangGraph 的一个入门应用但它几乎覆盖了图编排里所有关键概念状态管理、条件边、循环、结构化输出和工具集成同时兼具使用的价值。做完这个项目之后再去做客服机器人、数据分析报告生成器这类 Agent思路基本是互通的。如果看完全文想动手试一下我建议你先跑通基线的五节点流程再根据自己的写作场景调整提示词的约束方式。第一次跑通的时候看着 AI 在评审意见和修订稿之间来回迭代那种“它真的在认真改稿”的感觉还挺有成就感的。
阅读完成 · 觉得有帮助?