在 Windows 上用 venv 装 NovelForge 0.9.8,这套 AI 长篇小说写作工具的后端起来了,建出 11 张表,19 种卡片类型和 31 条提示词都进了库,7 个内置工作流一条没进去。

启动日志里 7 行都是同一句 Datetime values must have timezone information,装到的 sqlmodel 是 0.0.48。

NovelForge 0.9.8 装完怎么建出第一张卡片?

后端是 FastAPI 加 SQLite,前端是 Vue 加 Electron,两边分开装。数据全在 backend/novelforge.db 一个文件里,不装 Postgres 也能跑,知识图谱默认走 SQLite,Neo4j 是可选项。

cd NovelForge/backend
python -m venv ../nfenv
../nfenv/Scripts/python.exe -V        ->  Python 3.13.14
../nfenv/Scripts/python.exe -m pip install -r requirements.txt
cp .env.example .env
../nfenv/Scripts/python.exe main.py

装依赖花了 1 分 54 秒,requirements.txt 里列的是 fastapi、sqlmodel、langchain 和几个 provider 适配包。启动时跑一遍初始化器,提示词、卡片类型、知识库都成功,只有工作流那一步全挂。

命令输出耗时或退出码
pip install -r requirements.txt无报错1 分 54 秒
python main.pyNovelForge 后端启动完成!退出码 0
curl -X POST /api/projects/{"status":"success","data":{"name":"雪落长安","id":1}}HTTP 200

库里的实际数量和界面上能看到的一致,只有工作流是 0。

表数量: 11
card | cardtype | foreshadowitem | kgrelation | knowledge | llmconfig
 | nodeexecutionstate | project | prompt | workflow | workflowrun

cardtype 19
prompt 31
workflow 0
openapi: 94 路径 | 131 schema

19 种卡片类型按写作流程排好了序,从立意一路排到成稿,最后三类放素材和通用内容。

阶段卡片类型
立意作品标签、金手指、一句话梗概
骨架故事大纲、世界观设定、核心蓝图
分卷分卷大纲、写作指南、阶段大纲
成稿章节大纲、章节正文、内容审核卡片
素材角色卡、场景卡、组织卡、物品卡、概念卡
通用通用文本、文件夹

前端这条路不顺。npm install 跑到 6 分 36 秒被拦下来,node_modules 里有 685 个包、占 667 MB,.bin 目录是空的。

npm install  ->  6m 36s
npm error [safe-delete][SAFE_DELETE_BULK_CONFIRM_REQUIRED] {"count":50,"threshold":50,"scope":"turn"}
ls node_modules | wc -l  ->  685
du -sh node_modules      ->  667M
ls node_modules/.bin     ->  (空)

没有 bin 链接就跑不了 npm run dev:web,cross-env 会报找不到命令。绕过这一步直接调 vite 的入口即可,680 毫秒就绪。

VITE_APP_PLATFORM=web node node_modules/vite/bin/vite.js dev -c vite.config.web.ts
VITE v6.3.5  ready in 680 ms
Local: http://localhost:5173/

NovelForge 报 Datetime values must have timezone information 是什么原因?

第一次启动就报错,而且是每个 .wf 文件报一次。

grep -c "初始化工作流 .* 失败" backend.log  ->  7

2026-10-21 05:26:32.361 | ERROR | app.bootstrap.workflows:init_workflows:143
- 初始化工作流 世界观 失败: (builtins.ValueError) Datetime values must have timezone
information. Use datetime.now(timezone.utc), or annotate the field with NaiveDatetime
for naive storage.
[SQL: INSERT INTO workflow (name, description, dsl_version, is_built_in, is_active,
definition_code, is_template, template_category, keep_run_history, triggers_cache,
created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]

启动收尾还有一条清理任务报同一句错,它的 SQL 是 SELECT workflowrun.id FROM workflowrun WHERE workflowrun.finished_at < ?。

版本对不上是根因的一半。requirements.txt 里 sqlmodel 只写名字不写版本,装出来是 0.0.48。

pip show sqlmodel sqlalchemy pydantic
Name: sqlmodel    Version: 0.0.48
Name: SQLAlchemy  Version: 2.1.5
Name: pydantic    Version: 2.14.1

models.py 里时间字段两种写法混着用。Prompt 的 created_at 显式写了 sa_column=Column(sa.DateTime),所以提示词全部入库,没报错。剩下 6 张表用裸的 datetime = Field(default_factory=datetime.now),一共 14 处。

表裸 datetime 字段其中可空
Workflow20
WorkflowRun32
NodeExecutionState42
KGRelation20
ForeshadowItem21
Card10
合计145

裸字段走 SQLModel 推断出来的类型,0.0.48 在绑定参数时要求值带时区;datetime.now 给的是不带时区的值,插入就抛 ValueError。显式写了 sa_column=Column(sa.DateTime) 的字段绕过这层校验,所以同一个文件里两种写法一个能写一个不能写。

models.py 里 14 个时间字段怎么改?

改法照抄 Prompt 表:给裸字段补上 sa_column=Column(sa.DateTime)。非空的顺带加 server_default,可空的只指定类型。

# 改之前
created_at: datetime = Field(default_factory=datetime.now, nullable=False)

# 改之后
created_at: datetime = Field(
    default_factory=datetime.now,
    sa_column=Column(sa.DateTime, nullable=False, server_default=sa.text("CURRENT_TIMESTAMP")),
)

# 可空字段
started_at: Optional[datetime] = Field(default=None, sa_column=Column(sa.DateTime))

批量脚本按非空、可空两种模式匹配,在 0.9.8 原始的 models.py 上跑一遍就替换完,输出两行。

替换非空: 9 替换可空: 5
语法 OK

删掉 novelforge.db 用空库重起一次,验证是不是这 12 处的问题。

2026-10-21 05:30:32.131 | INFO | 已创建内置工作流: 项目创建·雪花创作法 (id=7)
2026-10-21 05:30:32.256 | INFO | app.bootstrap.workflows:init_workflows:149
- 工作流初始化完成: +7, ~0, -0
2026-10-21 05:30:32.259 | INFO | NovelForge 后端启动完成!
ERROR 数: 0

9 + 5 = 14,跟前面按表数出来的 14 处对得上。修复前后差别只在字段声明,表结构没动,老的 db 文件也能直接用。

指标修复前修复后差值
workflow 表行数077 - 0 = 7
启动报错行数700 - 7 = -7
清理任务报错100 - 1 = -1

NovelForge 的 7 个内置工作流都在哪、代码有多长?

7 个 .wf 文件放在 backend/app/bootstrap/workflows 下,启动时读出来写进 workflow 表的 definition_code 字段。

工作流代码行数definition_code 字符数硬编码 id
世界观448840
分卷大纲10522550
拆书工作流309102632
核心蓝图8517750
辩论测试8625041
阶段大纲7022150
项目创建·雪花创作法619200
合计760208163

代码不是 JSON 配置,是带 #@node 标记的 Python 片段,标记之间的几行在解析后变成工作流节点。世界观那 44 行从触发器一路写到清空来源列表。

#@node(description="触发器:监听世界观设定卡片保存")
trigger = Trigger.CardSaved(card_type="世界观设定")
# ...

#@node(description="使用表达式节点提取组织列表")
organizations = Logic.Expression(
    expression="card.content.world_view.social_system.major_power_camps or []"
  )
# ...

#@node(description="批量创建组织卡片(父卡片为当前世界观卡片)")
org_cards = Card.BatchUpsert(
    project_id=trigger.project_id,
    parent_id=trigger.card_id,
    card_type="组织卡",
    title_template="{item.name}",
    items=organizations.result
  )
# ...

最后一步是 Card.Update,把 major_power_camps 清空,这样卡片第二次保存时不会重复建一遍组织卡。工作流编辑器里的节点库按用途分 7 组,一共 24 个节点。

节点分组数量代表性节点
卡片操作7Card.Read、Card.Create、Card.BatchUpsert
逻辑控制6Logic.Expression、Logic.Assert、Logic.SelectLLM
AI 生成5AI.StructuredGenerate、AI.Debate
触发器2Trigger.CardSaved、Trigger.ProjectCreated
示例节点2Example.Process、Example.BatchProcess
小说处理1Novel.Load
数据处理1Prompt.Load
合计24

卡片生成的约束来自后端 pydantic 模型,31 个响应模型序列化后合计 28198 字节、146 个字段。

schema                         字节   字段   必填
CharacterCard                3048   11    7
VolumeOutline                2639   10    8
Blueprint                    2513    5    5
ReviewResultCardContent      1770   13    8
StageLine                    1593    8    8
ChapterOutline                904    6    6

总字节: 28198  平均: 910  字段总数: 146  模型数: 31

用 template=snowflake 和空白项目建出来差多少?

工作流不是建完项目就自动跑,要看触发条件。项目创建事件对应的触发器写在 workflow.triggers_cache 里,只有匹配到 snowflake 才动。

{"event": "project.created", "match": {"template": "snowflake"}}

不带 template 建项目,日志只有一行,卡片库里是空的。

[工作流触发] project.created - 空白项目,不触发工作流
GET /api/projects/1/cards  ->  卡片数: 0

带 template=snowflake 再建一次,雪花创作法工作流被触发,自动落 6 张卡。

POST /api/projects/  {"name":"雪落长安","template":"snowflake"}
-> {"status":"success","data":{"name":"雪落长安","id":1}}

卡片数: 6
 1 作品标签
 2 金手指
 3 一句话梗概
 4 故事大纲
 5 世界观设定
 6 核心蓝图

同样的请求在修复前是 500,因为触发工作流要先写一条运行记录进 workflowrun 表,而那张表的 3 个时间字段都是裸字段。

sqlalchemy.exc.PendingRollbackError: This Session's transaction has been rolled back
due to a previous exception during flush.
Original exception was: (builtins.ValueError) Datetime values must have timezone information.
[SQL: INSERT INTO workflowrun (workflow_id, definition_version, status, scope_json,
params_json, idempotency_key, state_json, error_json, max_execution_time, created_at,
started_at, finished_at, summary_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)]
建法结果卡片数
空白项目(不带 template)日志「空白项目,不触发工作流」0
template=snowflake,修复前500 Internal Server Error0
template=snowflake,修复后success6

这 6 张卡是空的骨架,字段按各自 schema 渲染出来。核心蓝图的编辑器里能看到总卷数、角色设计思考、场景设计思考三组字段,角色卡和场景卡是折叠的子表。

NovelForge 校验通过的拆书工作流为什么跑不起来?

内置工作流里写死了作者自己环境的 id,只有跟着触发器走的那几个不用改。

拆书工作流        project_id=10(第 8 行)  llm_config_id=5(第 14 行)   硬编码 2 处
辩论测试          project_id=11(第 14 行)                              硬编码 1 处
世界观/分卷大纲/核心蓝图/阶段大纲/项目创建    用 trigger.project_id    硬编码 0 处
7 个文件合计                                                          硬编码 3 处

点校验代码会返回干净结果,因为校验只查节点名和语法,不查这些 id 在库里存不存在。

POST /api/workflows/3/validate
{"is_valid":true,"errors":[],"warnings":[]}

真跑起来第一步就停。

GET /api/workflows/3/execute-stream
{"type": "error", "statement": {"variable": "project",
 "code": "Logic.SelectProject(project_id=10)", "line": 8},
 "error": "项目不存在: id=10, name=None"}

GET /api/workflows/5/execute-stream
{"type": "error", "error": "项目不存在: id=11, name=None", "message": "工作流执行失败"}

SELECT id FROM project  ->  1

这次只建过 1 个项目,id 是 1,10 和 11 都查不到。拆书工作流第 14 行还有 Logic.SelectLLM(llm_config_id=5),llmconfig 表里原本是空的,配上模型之后才有 1 行,id 也是 1。

其余几个工作流的 project_id 都写成 trigger.project_id,llm_config_id 写成 settings.result.llm_config_id,跟着触发上下文走,换个项目不用改代码。

NovelForge 在什么条件下会失败?

四种情况会直接失败,都来自这次实际碰到的报错。

触发条件现象(报错原文)对应步骤
requirements.txt 不锁 sqlmodel 版本Datetime values must have timezone information首次启动
只改 workflow 表,没改 workflowrunPendingRollbackError,接口返回 500用 snowflake 建项目
直接跑拆书工作流项目不存在: id=10, name=None执行工作流
直接跑辩论测试项目不存在: id=11, name=None执行工作流

只在 Windows、Python 3.13.14 的 venv 里用 main.py 启动测过,没在 macOS、Linux、Docker 里复测。

  • 未验证:接上模型后一张卡能生成多少字。前置卡片为空时,卡片编辑器的 AI 生成按钮是灰的,没有跑出生成结果。
  • 未验证:各类卡片里的正文编辑器和审核卡片联动。只验证了建卡、schema 渲染和字段表单。
  • 未验证:Electron 桌面版。只用 vite 起了浏览器版本,打包产物没测。
  • 未验证:关系图谱。kgrelation 表建好了,但没往里写过数据。

如果你打算直接用发布版 exe,本文的依赖版本和 sqlmodel 相关结论不能照搬,那条路径没有测。