ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于Harness架构的多Agent协作:搭建AI网文自动写作流水线

基于Harness架构的多Agent协作:搭建AI网文自动写作流水线

简介:面向零基础网文写作者与老书虫的AI辅助小说创作方案,采用Harness架构将创作流程拆解为扫榜、拆文、写作、去AI味、封面生成等专项Agent,覆盖创作全流程。包内共103个文件,以Markdown笔记、Python脚本、JSON配置、PNG样例图、文本说明为主,整体336KB,轻量易部署。已有36人学习,适合网文爱好者与小型工作室。资源内置百万级语料微调写作模型与27类文风调控权重,支持起点、晋江、番茄等平台调性;去AI味模块采用三轮对抗式重写,封面生成集成SDXL定制LoRA模型。所有Agent通过统一消息总线通信,可自由增删模块,附带Docker部署脚本、GPU加速指南及四大品类500组训练提示词库,能构建从灵感到数据看板的端到端工作流。

1. 这个 .zip 不是小说成品,而是一条 AI 网文流水线:Harness 架构想解决什么

拿到那个.zip之前,我以为里面是一本写完的网文。解压之后才发现,交付物不是小说,而是一条“能把网文从零写到上架”的 AI 写作流水线:一个用 Harness 架构思想搭出来的多专项 Agent 协作工程。它里面没有动辄几万字的正文,只有几段 Python 脚本、一批 prompt 模板、一个状态文件,以及一组各管一摊的专项 Agent——有人负责世界观,有人负责角色,有人负责章节。对完全没有写作经验的小白,它把“想故事”变成了“填任务卡”;对读过几千本的老书虫,它把脑子里那些黑匣子式的灵感,拆成了可干预、可修改的中间产物。这篇文章会把这条流水线拆开,告诉你 Harness 架构思想到底是什么,多个 Agent 怎么协作,以及从解压到跑通要躲开哪些坑。

2. Harness 与专项 Agent 的分工:为什么单个 Agent 写网文会越写越崩

先说结论:Harness 不是某个新模型,也不是某个必须引入的重型框架,而是一种“把多个 Agent 组织起来干活”的架构思想。很多开源项目喜欢给自己加harness后缀,比如 DeepSeek Harness、某某 Harness,其实都在做同一件事:在模型调用外层加一套可控制的流程,把“谁先跑、传什么、共享什么、失败怎么办”全部定下来。网文写作正好是这类架构最典型的应用场景,因为单 Agent 写网文的翻车方式大家太熟悉了。

2.1 Harness 和 Agent 的区别:编排层才是多 Agent 协作的主心骨

做 Agent 开发的人经常问:harness 和 agent 到底有什么区别?我的回答是:Agent 是干活的人,Harness 是工地上的项目经理加图纸加安全制度。一个 Agent 本质上是“一个大模型 + 一份 prompt + 一次调用”,它只能完成一件事:给一个输入,产出一个输出。你让它写一千字开篇,它写得出来;但你让它接着写十万字,它就开始出现“三章前主角还叫林澈,五章后变林彻”这种低级问题。原因是单个 Agent 的上下文窗口有限,记忆会漂移,而且它没有“这是第几卷、这条伏笔埋了没有、这个配角是不是已经死了”这种全局状态。

Harness 的价值就在这里。它不负责写小说,它负责管理参与写小说的所有 Agent:决定谁先执行,把上一个 Agent 的结构化输出变成下一个 Agent 的输入,把共享状态(世界观、人物卡、大纲)集中放在一个地方,还能在关键节点停下来等人确认。网文写作最怕的不是某一章写得不好,而是前后不一致、节奏混乱、人物行为逻辑断裂。这些问题靠单个模型本身解决不了,只能靠编排层从流程上兜住。

很多人第一次看到“Harness 架构思想”觉得高深,其实落到代码上,它是一个循环、一个状态字典、一张执行顺序表。后面我会给一个 30 行的最小调度循环,先记住这句话:Harness 是把多个 Agent 串成流水线的胶水,也是防失控的安全带。

2.2 一个网文 Harness 的角色表:谁产出世界观,谁产出章,谁又负责当“监工”

在动手写代码之前,先把职责边界画清楚。常见的落地做法是拆成六个专项 Agent,每一个只负责一个窄任务,产出必须是结构化数据。

Agent核心职责输入输出
世界观 Agent构建力量体系、地理、势力、核心冲突用户一句“创作愿望”world.json结构化设定
角色 Agent设计主角、配角、反派的人物卡world.json+ 用户人设偏好characters.json人物档案
大纲 Agent把设定拆成卷和章节的节奏world.json+characters.jsonoutline.json每章目标
章节 Agent按大纲产出正文草稿世界观摘要 + 角色卡 + 本章大纲chapter_{n}.md
一致性审查 Agent检查人名、时间线、战力、约定是否冲突最新章 + 历史设定问题清单 + 修改建议
润色 Agent文风打磨、节奏调整、口语化原始章 +style_guide.md可发布的章稿

表格里最容易被忽略的是“一致性审查 Agent”。很多初版方案只有前四个,结果写出来的东西照样前后打架。后来我学乖了,把审查 Agent 单独拆出来,让它在每个章节写完后跑一遍,专门当监工。为什么不让章节 Agent 自己审查?因为生成和评估是两个方向,放在同一个 prompt 里会互相干扰:模型既要写得热血,又要理性核对,最后往往两头都不到位。

除了职责拆分,还要注意不同 Agent 的参数差异。世界观 Agent 需要发散,temperature可以调到 0.9;章节写作要稳定,temperature调到 0.7 左右;一致性审查必须精准,temperature接近 0。这就是为什么不能所有 Agent 共用一个模型配置。

2.3 最小调度循环:用 30 行 Python 把 Agent 串起来

下面这个骨架是所有流程的核心。它做的事情很简单:按照顺序执行 Agent,每个 Agent 从共享状态里取输入,产出的结果写回状态字典,失败时重试。

# harness_core/scheduler.py from dataclasses import dataclass, field from typing import Dict, Callable @dataclass class Harness: agents: Dict[str, Callable] # 每个专项Agent的入口函数 state: Dict = field(default_factory=dict) # 全局共享记忆 order: list = None # 执行顺序,如 ["world", "characters", "outline"] max_retries: int = 2 # 单个Agent失败后的重试上限 def run(self, stop_after=None): if self.order is None: raise RuntimeError("Harness 必须指定 agent 执行顺序") for idx, agent_name in enumerate(self.order): if stop_after and idx >= stop_after: break for attempt in range(self.max_retries + 1): try: output = self.agents[agent_name](self.state) self.state[agent_name] = output break except Exception as e: if attempt == self.max_retries: raise RuntimeError(f"{agent_name} 连续失败 {self.max_retries+1} 次: {e}") print(f"[retry] {agent_name} 失败,正在重试…") return self.state

这段代码有四个参数值得细看。agents是每个 Agent 的人口函数,实际工程里它们通常会读prompt_templates/下的模板、调用大模型接口、再把结果解析成字典。state是唯一真源,所有 Agent 都从它读输入、写回输出,不要搞第二套状态。order是执行顺序,网文流水线一般固定为世界观 → 角色 → 大纲 → 章节,但你可以通过改这个列表来决定先写哪部分。max_retries必须限制,我看过有人默认设成 3,结果大模型接口一次超时,整条流水线卡了五分钟;实际上重试 1 次就够了,连续失败说明 prompt 或接口有问题,再重试也是烧钱。

这个骨架看起来简单,但它已经具备了断点能力:stop_after参数可以在跑完某个 Agent 后停下来,让人检查state里的产物再继续。很多人以为多 AI 协作一定要上分布式框架,实际上网文创作这种场景,十几章的量级一个文件型状态字典完全够用。

3. 把这套 .zip 跑起来:目录解压、配置模型密钥与第一个 Agent 调用

聊完架构思想,现在进入落地。拿到.zip的第一件事是解压,然后按目录结构认识工程。不要一上来就双击.py,先把每个目录为什么存在搞清楚。

3.1 交付物目录长什么样:目录设计即 Agent 边界

我见过不少 Agent 工程把提示词、脚本、状态全混在一起,改一个 prompt 要翻半天代码。这个.zip代表了一种更好的做法:目录划分直接对应 Agent 边界。

novel_harness/ ├── start.py # 一键启动入口 ├── requirements.txt # 依赖清单:requests、pyyaml ├── config.yaml # 模型端点、密钥、temperature 默认值 ├── llm_client.py # 统一的大模型调用封装 ├── harness_core/ │ ├── scheduler.py # 上面的最小调度循环 │ └── state_manager.py # 共享记忆读写与变更日志 ├── agents/ │ ├── world_builder.py │ ├── character_designer.py │ ├── outline_creator.py │ ├── chapter_writer.py │ └── consistency_checker.py ├── prompt_templates/ # 所有 prompt 都在这里,改提示词不用动代码 ├── state/ # 运行时生成,不要手工编辑 │ ├── world.json │ ├── characters.json │ ├── outline.json │ └── changes.log ├── overrides/ # 老书虫的追加要求,优先级最高 └── output/ # 生成的章节 Markdown

这个目录结构最值得学习的是prompt_templates/和overrides/。前者把所有提示词从代码里拿出来,新手改设定不用找逻辑;后者是给用户“插嘴”的地方,后面会细讲。state/不用手工维护,但changes.log很重要,它记录每一步谁改了什么设定,遇到问题能回溯是哪儿改崩的。

3.2 初始化环境与模型接入:DeepSeek Harness 的常见调用姿势

解压之后先建虚拟环境,不要直接往系统 Python 里装依赖,避免污染其他项目。Mac/Linux 和 Windows 的激活命令不一样,我一般会在start.py里自动做这一步,但手工跑一遍有助于理解。

unzip novel_harness.zip cd novel_harness python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate pip install -r requirements.txt

依赖通常只有两个:requests和pyyaml,因为大模型调用建议走 HTTP,不引入重型 SDK。模型接入是这套工程里最容易出问题的地方。常见做法是把“调模型”封装成一个统一函数,所有 Agent 都走这个入口,而不是每个 Agent 自己去拼请求。

# llm_client.py import os, yaml, requests def load_config(path="config.yaml"): with open(path, encoding="utf-8") as f: return yaml.safe_load(f) def call_llm(messages, temperature=0.8, max_tokens=1500): cfg = load_config()["model"] resp = requests.post( cfg["base_url"] + "/chat/completions", headers={"Authorization": f"Bearer {cfg['api_key']}"}, json={ "model": cfg["name"], "messages": messages, "temperature": temperature, "max_tokens": max_tokens }, timeout=cfg.get("timeout", 120), ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"]

这里三个参数你需要根据自己的场景调。temperature是生成随机性的核心:世界观任务给 0.9,章节写作给 0.7,一致性审查给 0.1,不要全用默认值。max_tokens要按产出长度来:写一章正文至少 2000,提取人物卡 500 就够,设置太短会被截断成半句话。timeout要留到 120 秒,因为长文生成经常超过 60 秒,提前断开只会浪费一次请求。

如果你用的是 DeepSeek 这类 OpenAI 兼容接口,config.yaml里的base_url换成对应地址就可以,/chat/completions的请求结构不变。也可以把它指向本地模型网关,代码不用改。

3.3 跑通“世界观 Agent”:从一份 prompt 模板到一份结构化设定

第一个要跑通的是世界观 Agent。这个 Agent 的任务是把用户一句“创作愿望”扩展成一份可用于后续所有 Agent 的结构化设定。它依赖一份模板文件,而不是把 prompt 写在代码里:

你是网文世界观架构师。请基于以下“创作愿望”生成世界观设定。 创作愿望: {user_seed} 要求: 1. 输出必须是JSON,字段为:world_name, power_system, geography, factions, history, core_conflict 2. 每个字段不超过100字,使用中文 3. 必须把“冲突”写得具体,不要只说“正邪对抗” 4. 如果有{extra_requirements},必须合并进对应字段

对应的 Agent 代码如下:

# agents/world_builder.py from llm_client import call_llm def build_world(seed: str, extra: str = "") -> dict: with open("prompt_templates/world_builder.md", encoding="utf-8") as f: template = f.read() prompt = template.replace("{user_seed}", seed).replace("{extra_requirements}", extra) text = call_llm( [{"role": "user", "content": prompt}], temperature=0.9, max_tokens=800, ) # 只提取JSON块,防止模型在回答里加点评。 import json, re match = re.search(r"\{.*\}", text, re.S) world = json.loads(match.group(0)) with open("state/world.json", "w", encoding="utf-8") as f: json.dump(world, f, ensure_ascii=False, indent=2) return world

这里有几个细节新手容易翻车。用replace而不是 f-string,是因为模板里有大量花括号,直接格式化会炸。用正则先抠出 JSON 块,是因为很多模型会在 JSON 外面包一段“好的,我来帮您”或者 markdown 代码块标记,直接json.loads会报错。state/world.json一旦生成,后面所有 Agent 都从它取设定,所以这个文件是整个流水线的第一块基石。

跑通后你会看到state/目录下多了一个world.json,里面是结构化的世界观。到这一步,你已经完成了“输入一句话,产出设定库”的闭环。

4. 让多个专项 Agent 协作产章:状态文件、任务卡与断点续写

单个 Agent 跑通不算本事,多个 Agent 协作才是这套工程的核心。很多单 Agent 写网文写到一万字就开始飘,是因为它在脑内维护人物小传;而多 Agent 流水线的核心是把“记忆”搬到磁盘上,用状态文件共享信息。

4.1 用 state.json 当共享记忆:每个 Agent 只读自己需要的那一块

我在自己的工程里会写一个state_manager.py,专门负责状态读写。它解决两个问题:一是防止所有 Agent 都往全局字典塞东西导致混乱,二是实现“上下文裁剪”——每个 Agent 只拿自己需要的那一小块设定,而不是把整个世界观灌进 prompt。

# harness_core/state_manager.py import json, os, hashlib STATE_DIR = "state" def _path(name): return os.path.join(STATE_DIR, name + ".json") def get_slice(name, keys=None): with open(_path(name), encoding="utf-8") as f: data = json.load(f) if keys: return {k: data[k] for k in keys if k in data} return data def merge_slice(name, updates): with open(_path(name), encoding="utf-8") as f: data = json.load(f) data.update(updates) with open(_path(name), "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False, indent=2) # 记录一行变更日志,方便回溯哪一步改了设定 with open(os.path.join(STATE_DIR, "changes.log"), "a", encoding="utf-8") as log: log.write(hashlib.sha256(json.dumps(updates, ensure_ascii=False).encode()).hexdigest()[:8] + " " + name + "\n")

这个模块的get_slice支持只读指定字段。例如章节 Agent 写作时,只需要power_system和factions,你就不必将整个历史设定都塞进去。这能显著降低 token 消耗,也能减少模型被无关信息误导的概率。merge_slice在写入时同步记录变更日志,每次 Agent 改了设定都能定位到具体是哪一步做的。

补充一个参数经验:当state/world.json超过几千字时,不要全量读取。可以在config.yaml里设max_context_chars: 4000,然后对json.dumps结果做截断,并在 prompt 末尾补一句“以下设定超出长度已省略,只保留与本幕相关部分”。粗暴,但有效。

4.2 从大纲到第一章:完整跑一节“世界设定 -> 角色 -> 章节”的链路

现在把前面的模块串成一个真正的流水线。顺序是:先建世界观,再设计角色,再写大纲,最后循环写章节。

# pipeline.py from agents.world_builder import build_world from agents.character_designer import design_characters from agents.outline_creator import create_outline from agents.chapter_writer import write_chapter from harness_core.state_manager import get_slice def run_first_batch(seed, num_chapters=3): build_world(seed) design_characters() create_outline(num_chapters) for ch in range(1, num_chapters + 1): # 每次只取该章大纲和必要设定,避免上下文膨胀 world = get_slice("world", ["power_system", "factions"]) chars = get_slice("characters") outline = get_slice("outline", [f"chapter_{ch}"]) md = write_chapter(ch, world, chars, outline) with open(f"output/chapter_{ch}.md", "w", encoding="utf-8") as f: f.write(md)

在这里,角色 Agent 的输出characters.json和大纲 Agent 的输出outline.json是承上启下的关键。大纲的结构建议用下面这种紧凑格式,让章节 Agent 一眼看懂本章任务:

{ "chapter_1": { "title": "第一巴掌", "goal": "让主角穿越后第一次用新获得的能力解决问题", "climax": "主角当众揭穿村长私吞灵石", "twist": "看似普通的村童其实是上界探子" } }

章节 Agent 拿到这份任务卡后,再结合world和chars切片生成正文。注意这里只传了outline.chapter_1,没有传整个大纲文件,目的是防止章节 Agent“提前知道太多”导致剧透式的平淡写法。

这个流程跑完,你会得到output/chapter_1.md。但此时千万不要急着让整条流水线刷完十卷。第一次跑通后,先看一章,再决定要不要把num_chapters调大。

4.3 人工干预点:老书虫在哪个环节塞私货最有效

全自动不是目标,落地要给用户“插嘴”的机会。尤其老书虫脑子里有大量网文套路和喜好,如果 Harness 不让他们干预,生成的文再流畅也是死的。这就是overrides/目录存在的意义。

# harness_core/read_override.py import os def read_override(slot: str) -> str: path = f"overrides/{slot}.md" if os.path.exists(path): with open(path, encoding="utf-8") as f: return f.read() return "" def inject_override(prompt: str, override: str) -> str: if override: prompt += "\n\n### 你的追加要求(优先级最高)\n" + override return prompt

这个机制的参数很灵活。slot可以对应某个具体章节,比如overrides/chapter_2.md;也可以对应某个角色,比如overrides/character_leader.md。Harness 在生成下一个节点前,把用户写好的要求追加到 prompt 末尾,并注明“优先级最高”。为什么放在末尾?因为在长上下文里,模型对末尾内容的注意力往往最强,放在开头容易被后续指令稀释。

这一步是整套架构的分水岭:它把“多个 AI 协作”变成了“人 + 多个 AI 协作”。小白可以只填空,老书虫可以在关键设定上亲自把关,两边都不痛苦。

5. 多 Agent 写作最容易踩的 5 个坑:现象、原因与排查解法

跑通和跑好是两回事。我在这类 Harness 工程上踩过的坑,几乎都集中在上下文管理和工程化交付上。下面这 5 条按“现象 → 原因 → 解决”写清楚,希望能帮你少走弯路。

5.1 上下文与记忆类故障:设定漂移、风格漂移

坑 1:主角写到一半改名,战力体系崩了。现象:第 3 章主角还叫“林澈”,第 5 章变成“林彻”;第 2 章刚说了筑基后期,第 4 章直接金丹。原因:章节 Agent 只拿到了当前章的大纲,没有从state里读取角色卡和最新境界,或者角色卡太长被上下文裁剪挤掉了。解决:在章节写作的 prompt 里强制加一段“开始正文前,先列出本章涉及的人物与当前状态”,让模型先输出一段核对清单再动笔。同时让一致性审查 Agent 在每章写完后跑一次,发现不一致就打回重写。不要以为这是多余,网文读者对这种人名和战力错误非常敏感。

坑 2:每章文风不一样,像换了个人在写。现象:第 2 章热血打斗,第 3 章突然抒情散文化,对话方式也变了。原因:多个 Agent 各自为政,没有共享一份“风格指南”,或者风格指南写得太抽象,比如只有“要有网感”四个字。解决:在 State 里放一份style_guide.md,包含硬约束和软参考两部分。硬约束写死:第三人称、过去时、每章结尾停在悬念处;软参考给三段范例文字,明确告诉模型“学这个味道”。然后把它作为公共片段,注入到每个生成类 Agent 的 prompt 里。

5.2 工作流与工程化故障:环境跑不起来、token 烧得太快、用户指令被覆盖

坑 3:小白拿到.zip跑不起来,报ModuleNotFoundError。现象:双击start.py,控制台立刻报缺依赖。原因:交付物里没有requirements.txt,或者有但用户没激活虚拟环境就直接跑。解决:start.py开头自动检查缺少的包,提示“正在安装缺失依赖,请稍候”;不要只在文档里写命令,因为小白会在错误的 Python 环境里反复尝试。另外脚本内部用sys.executable而不是写死python,避免多版本混用。

坑 4:token 消耗爆炸,写三章烧掉一个月的预算。现象:每章生成前调了好几次模型,账单涨得飞快。原因:每个 Agent 都把整套世界观、全部角色卡、前面所有章节原文塞进 prompt;失败重试默认设成 3 次,一次超时接着再烧两次。解决:在state_manager.get_slice里做上下文裁剪,只传当前章节必要的字段;把max_retries从 3 降到 1;章节 Agent 输出时不要重复粘贴大纲原文。还有一个容易被忽视的点:一致性审查 Agent 不要读整章正文,只读“设定快照 + 新增章”就能判断,节省一半 token。

坑 5:老书虫的私货被 Agent 无视。现象:用户明确在overrides/里写“这章不要让女主角出场”,结果女主角还是出场打了一架。原因:用户指令放在 prompt 开头,模型在长文生成中把它当弱约束忽略了。解决:把 override 内容追加到 prompt 末尾,并标记“最高优先级”;生成后做一次关键词校验,如果指令包含“不出场”这类要求,检查输出里是否出现对应人名,出现则触发重写。这不是玄学,而是让用户追评成为硬约束的工程手段。

如果你排查时发现某一步莫名其妙,先别怀疑模型,去看state/changes.log。多 Agent 协作里 80% 的问题都能追溯到“上一个 Agent 写坏了状态文件”。

6. 进阶:把人工“改稿权”做成断点,让 Harness 跟着老书虫的思路走

6.1 检查点不是暂停,而是面向老书虫的“软路由”

我在自己的工程里加了一个检查点机制:每写完一章,流水线自动暂停,把当前章节的故事状态摘要写出来,然后读取老书虫对下一章的要求。这个机制不是用来打断生成,而是把人类经验插在模型最容易跑偏的地方。

# checkpoint.py from harness_core.state_manager import merge_slice import os def checkpoint_after_chapter(chapter_no): summary = f"第{chapter_no}章完成,当前人物关系/战力/已埋钩子见state/checkpoint.json" merge_slice("checkpoint", {f"chapter_{chapter_no}": summary}) note_file = f"overrides/next_chapter_{chapter_no + 1}.md" if not os.path.exists(note_file): with open(note_file, "w", encoding="utf-8") as f: f.write("# 你对下一章的要求\n\n") print(f"已生成 checkpoint {chapter_no},请编辑 {note_file} 后按回车继续") input()

这个断点设计的核心是“Harness 不要自作主张”。我一般每写 3 章停一次,让老书虫改一版“下一章方向”,再继续。看起来慢了,但实际上避免了后面 10 章全部推倒重写。很多人以为多 Agent 就是全自动,其实把它当成“带审稿环节的流水线”才不容易崩。

我以前迷信全自动,让 Harness 一口气跑完十章,结果主角性格曲线完全失控,女主中途换了三次名字。后来我把检查点加回去,才发现真正有价值的是“在关键处让人进来把关”,而不是让模型自己一条路走到黑。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表