news 2026/10/19 13:54:30

基于LLM的叙事Agent:多模块架构实现稳定长文故事生成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于LLM的叙事Agent:多模块架构实现稳定长文故事生成

先把话说清楚:这篇东西不是那种"拿着ChatGPT随便聊两句、把对话整理成一个故事"的玩具教程。我要聊的是,怎么基于LLM设计一个有真实叙事能力的AI Agent,让它能稳定产出多章回、有因果推进、有人物弧光的故事,而不是一写就崩、一长就跑偏。

这个需求最近找我聊的人特别多。有人想拿AI做互动小说,有人想给自己的游戏批量生成支线剧情,还有人想做一个"AI陪你共同写小说"的创作助手。他们拿大模型的API直接对接之后,普遍遇到一个问题:单段生成很惊艳,多段生成就彻底失控。角色性格会变、时间线会乱、前文埋的伏笔后文根本不记得。这就是典型的"语言模型"和"叙事系统"之间的鸿沟。

我过去一年反复折腾过这类系统,从简单的"一个Prompt + 长上下文"到后来拆成多模块的Agent架构,踩了不少坑。这篇文章就把我沉淀下来的核心认知、架构思路和调优经验一次讲透,适合那些有一定工程基础、想真正把LLM用进叙事场景的人。

1. 先认清问题:LLM擅长的是"接句子",不是"讲故事"

我们得先接受一个反直觉的事实:哪怕是最顶尖的LLM,它的底层能力也只是"给定上文预测下文概率"。这个东西被用在对话、摘要、改写上非常好用,但故事不是"一串概率合理的句子",故事是一个有约束的系统。

1.1 语言模型对"局部合理性"的执着会毁掉"全局一致性"

LLM天然倾向于让当前这段输出在局部看起来通顺、合理、有文采。你和它说"主角是个胆小的会计",它在这一段里写得很到位,会描写他汗手、犹豫、不敢反抗。但到第三章,你的故事需要他爆发,LLM大概率会让他在一段对话里突然变成硬汉,因为"这一刻的戏剧张力"在局部看是合理的,LLM从概率上会倾向于选择更"带感"的表达,而不会去核对这个人物前80%的情节里是怎么行动的。

我刚开始做多章回生成时,用的是一个很粗暴的方案:把前文全塞进上下文,然后让模型续写。写到第5章之后,人物行为的一致性已经像换了个人设。这不是因为模型笨,而是因为局部连贯和全局一致是两种不同的任务,前者是LLM的强项,后者需要额外的系统约束。

1.2 故事的隐形结构:因果链、节拍、角色动机

要构建叙事能力,第一步是把"故事"拆解成LLM能够遵循的显性结构。我自己用下来最顺手的拆法是这样:

  • 因果链:每一章事件必须是前一章事件的直接结果,同时要为后一章埋下种子。这是硬逻辑,不能靠模型自觉。
  • 节拍:故事不是匀速前进的,需要有铺垫、升高、高潮、回落。每个节拍对应一种"叙事压力值"。
  • 角色动机:每个主要角色在出场时要有一个"底层欲望"。后续所有行为都必须能被这个欲望解释,否则就是OOC(Out of Character,角色崩坏)。

工程上做的所有事,本质都是把这些隐形结构变成模型每轮生成时不可忽视的显式输入。

1.3 所以我们需要Agent,而不仅仅是一个Prompt

如果你只需要生成一个短篇,比如3000字以内,那确实靠精心写的Prompt就够。但一旦超过这个量级,单Prompt方案会同时撞上三堵墙:上下文窗口耗尽的墙、细节遗忘的墙、风格漂移的墙。

Agent的本质是把"生成"这个单一动作,拆成"规划-执行-检查-修正"多个环节。叙事Agent就是在生成故事时,先让一个模块做叙事设计,再让另一个模块把设计落地成文本,再让第三个模块检查落地质量。每个模块只做一件事,反而能避开LLM"什么都想干最后什么都干不干净"的毛病。

2. 架构上怎么拆:一个可落地的多模块叙事Agent设计

我踩过最贵的坑,就是把所有责任都压给一个超长Prompt。后来我参考了"编剧室"的工作方式——有人负责世界观、有人负责单集脚本、有人负责审稿——把这个流程映射成了Agent架构。下面是我目前在用的分工。

2.1 核心模块:Story Planner(故事规划器)

这个模块不生成正文,它只产出结构化叙事方案。每次动笔前,它需要输出一份JSON,包含:

  • 当前章回的目标(这一章要让故事推进到什么状态)
  • 冲突类型(人际冲突/环境冲突/内心冲突)
  • 主要出场角色及他们在此章的情绪状态
  • 本章要埋的伏笔和要回收的伏笔

为什么要单独拆出这个模块?因为LLM在"做计划"和"写正文"两种任务之间切换时,质量会互相拖累。你让模型既想情节又写具体句子,它很容易在句子里过度发挥,然后把计划偷偷改掉。拆开后,规划器只做纯逻辑推演,对格式稳定性要求极高,我给它的温度是0.2;写正文的模块温度是0.8。各司其职,效果立竿见影。

2.2 核心模块:Story Writer(故事落地器)

Writer负责把Planner的JSON方案转成可读的正文。它接收的东西不是"前文全文",而是经过处理的"前文摘要 + 角色状态 + 本章大纲"。

这里有个容易犯的错误:以为给全文越多越好。实际恰恰相反,上下文里的噪声会让Writers迷失重点。我给Writer的上下文一般控制在2400-3500个token,其中包含:

  • 一段压缩后的故事摘要(覆盖全局因果链)
  • 上一章的结尾段落(保证衔接感)
  • 当前角色的状态卡(性格标签、当前情绪、当前目标)
  • 本章的叙事节拍提示(铺垫-升高-高潮-回落里,本章处于哪个位置)

压缩摘要这个动作,最好单独用一个Summarizer模块来做,不能直接用Writer自己压缩。因为Writer在压缩时容易丢掉最能制造悬念的信息,它会把"主角发现戒指内侧刻着仇人的姓氏"这种关键细节在摘要里省掉,导致后文接不上。

2.3 核心模块:Continuity Checker(一致性检查器)

很多系统只做"生成",不做"校验"。但叙事场景里,校验模块才是质量的守门员。Continuity Checker拿到的输入是"角色状态卡 + 设定集 + 本章生成的正文",输出是一份问题清单。

它的检查项包括:

  • 角色行为是否违背已设定的人格特质
  • 时间线是否和全局年表冲突
  • 是否重复使用了已经被用过的桥段
  • 本章结尾是否留出了和后文衔接的钩子

这里有一个工程细节:Checker也必须是一个独立的LLM调用,而且要把温度调低,同时给它"挑刺优先"的System Prompt。如果让Writer自己检查自己,它会非常温柔,什么毛病都看不出来。我试过让同一个模型既写又审,它给我输出"这段描写很生动,无一致性问题",但我肉眼就能看出角色性别都变了。

2.4 三个模块之间的协作流:加权循环而不是一次性串行

初版架构我做成了一次性串行:Planner出方案,Writer写正文,Checker查问题。但很快发现,一次串行根本不够。

原因在于,Writer产出的正文往往和Planner的方案有偏差,而Checker发现的问题又会反过来要求调整方案。所以我改成了循环结构:Planner → Writer → Checker →(有问题则)回到Planner修正方案 → Writer重写 → Checker再查。循环上限设为3次,超过3次还没通过,就判定为"本章规划难度过高",强制触发降级策略:缩小本章目标,只推进一个核心事件,放弃次要支线。

这个循环看起来会增加API成本,但实际运行中,大约60%的章节在1-2轮内就能通过。真正需要3轮以上的,往往是规划器本身设计失误,这时候重写多少遍正文都没用,必须回到规划层。

3. 稳定性从哪来:状态管理、记忆系统和上下文控制

聊完了模块分工,接下来是数据层面的事。叙事Agent能不能稳定工作,一半取决于你怎么管理它的"记忆"和"当前状态"。

3.1 用状态机管理故事进度,而不是用"章节序号"

最简单的故事进度管理,是一个计数器:写到第几章了。但这个粒度太粗,无法支撑真正的叙事逻辑。我改成了状态机模型。

以最常见的"英雄之旅"结构做例子,我把进度定义为9个离散状态:

  1. Ordinary World(日常世界)
  2. Call to Adventure(冒险召唤)
  3. Refusal of the Call(拒绝召唤)
  4. Meeting the Mentor(遇见导师)
  5. Crossing the Threshold(跨越门槛)
  6. Tests, Allies, Enemies(试炼)
  7. Approach to the Inmost Cave(接近洞穴)
  8. Ordeal(磨难)
  9. Reward and Return(奖赏与回归)

Planning模块每次生成方案前,都要先读取当前处于哪个状态,然后本章的目标设置为"推进到下一个状态"。这样做最直接的好处是,故事不会在中间某个爽点上无限徘徊。没有状态机的约束时,LLM会特别喜欢反复写"角色陷入危险然后脱险"的桥段,因为这段戏剧张力高,局部概率大。有了状态机,你就能硬性打断它的重复冲动。

3.2 记忆分级:哪些进长期记忆,哪些只存在于本章

长期记忆是一个人设和因果链的总和,必须结构化存储,不能直接丢给模型让它自己记。我通常用JSON文件或数据库存三张表:

  • Character Table:角色ID、名字、核心欲望、性格标签(固定5个)、当前目标、当前情绪、和其他角色的关系web。
  • Plot Table:事件ID、所属章回、因果指向、涉及角色、是否已回收。
  • Setting Table:世界观常量、不可破坏的物理/社会规则。

每章结束后,由一个Memory Updater模块读取本章正文,自动增量更新这三张表。这个更新动作极其关键,因为它决定了下一章Agent眼中的世界是什么样。我遇到过一种情况:角色已经在第五章死了,第六章Planner还把它安排在出场列表里。后来查出来是Memory Updater漏更新了这个角色的状态。所以Memory Updater输出后,必须加一道校验:核对本章正文里所有有明确状态的实体,是否在表里同步更新。

3.3 上下文压缩的时机和策略:增量摘要比全文摘要可靠

很多人做长故事,上下文不够用了就做一次摘要,把前文压缩一下接着写。但"一次性摘要"方案有个隐蔽缺陷:摘要本身会丢失大量细节,而丢失的细节恰恰是后面埋的伏笔和人物关系的暗线。

我现在的做法是增量摘要(Rolling Summary)。每次新增一章,只把"新章正文 + 已有摘要"放进Summarizer,生成一份"更新的摘要",然后丢弃旧的摘要文本。这样一来,摘要的压缩是渐进式的,早期重要信息会被反复保留(因为每次摘要都基于旧摘要生成),而早期的噪声会在几轮之后被自然淘汰。

成本上,增量摘要比全文摘要多几次调用,但换来的是长故事前30章的一致性明显提升。我之前做过实验:同一个设定,用全文摘要方案跑到第20章,角色关系图已经更新得面目全非;换增量摘要后,第30章依然沿用了第一章节埋下的核心冲突,这是质的差别。

4. 叙事质量的判断:不能靠感觉,要有可量化的检查维度

要做工程化的叙事系统,就必须解决"质量怎么判"的问题。以前我们靠人读,每章都通读一遍,一部长篇下来能累掉半条命。后来我把质量检查拆成了一个个可以自动化的子任务。

4.1 硬性指标:一致性、因果性、钩子留存

我给自动检查模块定义了三类硬性指标:

  • 一致性(Consistency):本章出现的人名、地名、时间描述,是否和设定表冲突。这个是纯逻辑判断,让低温模型做精确引证。
  • 因果性(Causality):本章至少要有两个事件能被上一章找到原因,同时本章至少要有一个事件成为后文的原因。我要求Planner在方案里显式声明因果关系,Checker按图索骥。
  • 钩子留存(Hook Retention):本章的结尾是否提出了一个未解决的问题。如果Checker发现结尾的悬念已经被彻底解决,或者根本没有新的悬而未决项,就判定需要打回重写。

4.2 软性指标:情感弧线、张力曲线

硬性指标之上,还有一类软性指标,靠规则比较难量化,但仍然可以借助LLM做打分。我设计了一个带加权公式的评分卡:

  • 情感张力:本章节情绪强度是否呈上升或下降趋势,而非一条平线。打分区间0-5。
  • 节奏分布:是否有大段无冲突的平铺直叙。允许存在,但不能连续超过两章。
  • 角色主动性:主要角色在事件中是"推动者"还是"被推着走"。一个叙事合格的章节里,至少有一个主要角色做出了改变局势的主动决策。

总分 = 0.4×一致性 + 0.3×因果性 + 0.3×主动决策指数。低于阈值的章节直接进入重写流程,而不是进入下一章。这套打分逻辑一开始我觉得太机械,但实际跑下来,它能挡住最糟糕的20%-30%的输出,剩下的人力精力去处理更微妙的叙事问题。

4.3 半自动检查:模型打分 + 人工抽检的双轨制

完全靠自动检查也有盲区,特别是涉及隐喻、主题表达这种高层次叙事要素,LLM的打分并不可靠。我的方案是双轨:每章自动跑硬性指标,每5章做一次人工抽检,重点看主题是否跑偏、人物弧光是否有递进。

人工抽检时我会格外留意一件事:模型是否把"信息的复述"当成了"剧情的推进"。比如主角反复在想同一个问题,虽然每次措辞不同,但认知状态没有变化。这种章节往往符合所有自动指标,但它是一段注水内容。回归工程角度,我最终认可了一个折中方案——允许每3章存在一段"缓冲章节",这种章节不推进主线,只用来展示人物关系和世界日常,但对它的要求是必须在结尾埋一个新事件种子。

5. 调优过程中最常见的四类事故与解决的思路

最后聊点实战中高频踩坑的事故类型。这些坑不是偶然,而是叙事Agnet系统的结构性问题,几乎每个做这方向的人都会遇到。

5.1 事故一:角色OOC(性格崩坏)与它的根治方案

先回归那个最常见的状况:一个谨慎胆小的人设,在某个高压场景下,LLM让他突然当众顶撞反派。原因是局部语境里"顶撞"是最有戏剧张力的选择,模型选择了局部最优。

我的根治方案是"角色行为准则预写"。给每个主要角色建一个行为规则卡,写清楚三件事:他在压力下的典型反应、他在舒适区的典型反应、他绝对不会做的事。这个规则卡每次生成时都会进上下文,并且它的优先级被放在比正文示例更高的位置。另外,一旦Checker在检查中识别出OOC行为,我会让Memory Updater在更新角色表时,主动记录这个行为偏差,并在后续对话中提醒模型修正,避免同一个角色连续两章行为漂移。

5.2 事故二:剧情原地打转,章节之间没有推进感

这个特别容易发生在没有状态机约束的版本里。模型反复让角色面临相似的困境,看起来情节紧凑,实际上在原地踏步。

处理办法前面提过:引入状态机。每个章节都必须把推进状态作为最高优先级目标。如果规划器给出的本章目标无法让状态前进,写入规则的逻辑会在路由层拦截,直接拒绝执行。

还有一种特殊情况:故事角色被"困"在某一个场景是剧情的需要,比如主角必须在一个密闭空间里待一整个晚上。这种情况下状态机无法前进,我不会硬改设定,而是在章节结束时安排一个认知层面的推进——比如角色做了一次关键决定,或者对处境有了全新的认知。认知变化也是一种推进,但必须显式记录到摘要里,否则下章就会被当作没有推进。

5.3 事故三:上下文被灌满脏数据,模型反而"变笨"

一个很反直觉的发现:当你试图把更多前文信息塞给Writer时,生成的文本质量不是单调上升的,而是呈倒U型曲线。上下文中过多类似的段落、过多对话记录,会让模型沉迷于模仿前文的句式,丧失对当前目标的执行力。

我的解决方法是加一层Semantic Router(语义路由)。它负责在Writer调用前,从记忆系统里检索和本章目标最相关的信息:人物状态、上一章结尾、相关伏笔;再把不相关的细节果断丢弃。与其说这是"尽量多记",不如说这是"尽量留精华"。上下文是稀缺资源,要把每一寸空间都用来服务当前叙事目标,而不是服务对过往历史的敬意。

5.4 事故四:风格漂移,长故事越写越不像原来那本书

风格漂移和角色OOC不太一样。OOC是角色性格出问题,风格漂移是整个文本的语气、词汇偏好、节奏感发生变化。常见表现:前三章是冷峻的悬疑风格,写到第十章开始无意识冒出大量网络热词和轻快口吻。

在工程上,我把"风格锚点"做成了系统级约束,独立于正文,不参与剧情逻辑,只影响输出文风。每一章生成前,交给Writer的不只是剧情方案,还要加上一段"风格参照段"(Style Anchor)。这段文本不长,约200-300字,来自第一章里最典型的三段描写。让Writer先吸收风格锚点,再写正文。同时,每次增量摘要时,我会把上文的风格描述单独提取出来,而不是混在情节摘要里。这样即使剧情摘要被压缩了,风格锚点仍然独立存在。

6. 如果想上手,第一步应该干什么

写了这么多架构和细节,最后给真正想学系统的人一个落地路线。

6.1 先做一个最小可用的单模块版本,再逐步拆分

我见到太多人一上来就要搭全套多Agent系统,结果管线太长,任何一个环节出错都不知道该调哪里。我的建议是分四步走:

第一步,用单Prompt生成一个完整短篇,5000-8000字。先感受一下LLM在纯生成模式下能稳定做到什么程度、在哪里崩。

第二步,加一个Plan前置:一个独立的调用负责写方案,一个负责写正文。这里你就已经能体会到"规划与执行分离"的力量。

第三步,加一个Checker做硬性校验,自动过滤明显的逻辑错误和OOC。这时你的故事从"看运气"变成"有质检"。

第四步,再把长期记忆、状态机、增量摘要加进去。这时的系统才称得上是Agent,因为它具备了持续的自我更新能力。

我最初就是被第二步启发的:同一个LLM,在单纯让它写正文 vs 让它先规划方案再写正文,前者的天马行空后者根本接不住。规划器把"灵感"变成了"蓝图",Writer要做的只是在蓝图里添加血肉,发挥反而更稳定。

6.2 记住一句话:好的叙事Agent,是不停地在给LLM维持一个"被约束的创作空间"

模型始终是那个擅长接句子的模型,它不会因为套上了"Agent"的名字就突然拥有真正的全局意识。Agent工程的价值,在于为模型构建一套外部的脚手架:状态机让它记住故事走到哪,角色卡让它记住人物是谁,Checker让它发现自己的盲区。每一次调用都在这个预设的约束网格里工作,它生成出来的东西自然就会越来越接近完整的叙事框架。

而你作为这个系统的设计者,真正的任务是理解"哪些约束必须铁腕执行、哪些地方又应该放给模型自由发挥"。这个平衡,是所有叙事Agent项目里最难把握、也最有意思的地方。调参调烦的时候,回来想想这篇的这几张表,路就会清晰很多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 13:54:29

STM32F103 DAC输出正弦波:定时器触发+DMA搬运实战

简介:这份资源面向嵌入式开发初学者与STM32学习者,围绕STM32F103ZET6内置12位DAC实现音频输出这一实验场景,帮助读者理解数字信号转模拟信号、WAV转C数组、定时器与DMA驱动播放等关键环节,并涉及外接功放与扬声器的硬件连接思路。…

作者头像 李华
网站建设 2026/10/19 13:54:29

LangChain+RAG实战:用LCEL构建端到端可维护的Chain流水线

说一个特别真实的阶段:Embedding 模型、向量库、Prompt 模板、大模型接口,这些单个组件你都能跑通,但一旦要把它们串成一个真正能用的 RAG 应用,代码立刻变得又臭又长。我最早做问答系统时就是这么过来的——先查向量库&#xff0…

作者头像 李华
网站建设 2026/10/19 13:54:04

生成式AI革新多模态信息检索:从跨模态对齐到落地实践

从最初的“搜关键词”到现在的“直接问”,信息检索这十年的变化比我预想的要快得多。尤其是生成式AI杀进来之后,多模态信息检索这个赛道彻底换了一套玩法——以前我们处理的是“文本进、文本出”,现在却是“图、文、音、视频混合进&#xff0…

作者头像 李华
网站建设 2026/10/19 13:53:03

开放式代码审查实践:从基础设施到审查维度与团队机制

1. 为什么“开放式代码审查”比单纯过一遍 MR 更值得落地先说一个我观察了很久的现象:很多团队的 code review 其实是在“走流程”。功能开发完,提一个 MR,指派给一位资深同事,对方忙的时候扫一眼,回一句“LGTM”&…

作者头像 李华
网站建设 2026/10/19 13:52:45

Windows下INI文件读写实战:配置管理最佳实践

1. INI 文件到底是什么:一种不该被遗忘的轻量配置方案提起 Windows 开发里的配置文件,很多朋友第一反应是注册表、XML、JSON,甚至现在流行的 YAML。但我在实际项目里折腾一圈之后发现,INI 文件在特定场景下仍然是最省心的选择&…

作者头像 李华
网站建设 2026/10/19 13:49:29

MATLAB SVD实战:从矩阵分解到图像压缩与降维的完整实现

简介:这份MATLAB SVD资源包面向学习线性代数、信号处理与图像处理的初学者及工程人员,围绕奇异值分解这一核心矩阵分解方法,提供可直接运行的代码示例与实验素材,帮助理解SVD的数学原理及其在信道估计、图像压缩去噪等场景中的落地…

作者头像 李华