Local-first 的 AI 内容写作工作流

0阅读11分钟

一篇草稿引发的信任危机

我在某个云端 AI 写作平台写过一篇技术复盘。平台在第三段自动续写了四句话,逻辑通顺,但其中一个关键数据是我还没确认的内部指标。我不敢用,又舍不得删——删了要重写,用了怕出错。更麻烦的是,我想回到两小时前的版本,发现平台的「历史版本」只保留了最近三次自动保存,中间恰好跳过了我改过术语的那一版。

这不是个别体验。ChatGPT、Jasper、Notion AI 这类工具把写作变成了「对话-复制-粘贴」的循环。内容存在别人的服务器上,版本靠平台的自动保存,格式在复制粘贴中丢失。当你习惯了在 IDE 里写代码、用 Git 追踪变更、靠 CI 验证构建,再来看内容创作的工具链,会发觉两者之间差了一整个工程化距离。

这篇文章要聊的,是一种不同的路径:把文章当代码一样管理,用 Markdown 做载体、Git 做版本控制、本地 LLM 做 AI 辅助、构建流程做质量关卡。不是要替代所有云端工具,而是给内容创作一条「本地优先」的退路。

Local-first 的三个前提假设

在展开工作流之前,有必要把底层假设说清楚。Local-first 不只是「把文件存本地」,它背后有三条判断。

数据主权归作者。你写的每一个字、每一次修改,都应该在你可控的存储里。不依赖某个 SaaS 平台是否还在运营、是否改了定价、是否把你的内容拿去训练模型。Markdown 文件存在你的磁盘上,十年后依然能用任何文本编辑器打开。

版本是内容的一部分。一篇文章不是「最终版」一个状态,而是从大纲到初稿到定稿的演进过程。Git 的 commit 记录天然适合承载这种演进,而且能精确到段落级别的 diff——这是任何「历史版本」功能都做不到的粒度。

AI 是助手,不是作者。本地 LLM 提供补全、改写、扩写建议,但最终决定权在人。这和 Copilot 辅助写代码的逻辑一致:它给你候选方案,你决定是否采纳、如何调整。

从 Docs-as-Code 到 Content-as-Code

Docs-as-Code 不是新概念。早在 2010 年代,技术文档领域就形成了「文档即代码」的实践:用 AsciiDoc 或 Markdown 写作,存进 Git 仓库,用 Sphinx、MkDocs、Docusaurus 这类静态站点生成器发布,CI/CD 自动构建和部署。

Content-as-Code 是这条线索的自然延伸。区别在于,Docs-as-Code 主要面向 API 文档、用户手册这类「说明性」内容,而 Content-as-Code 把同样的工程化思路扩展到博客、教程、案例分析、产品更新、内部复盘这些「叙述性」内容。

两者的共同点是对工具链的要求:

维度传统 CMSContent-as-Code
存储格式数据库 / 私有格式Markdown / YAML
版本控制平台内置(有限)Git(完整历史)
编辑环境平台 Web 编辑器任意文本编辑器 / IDE
预览方式平台预览 / 草稿模式本地 dev server
发布流程点击「发布」按钮Git push → CI/CD 自动部署
AI 辅助平台内置(不可控)自选本地 LLM / API
协作方式平台角色权限Git PR + Code Review
数据迁移导出困难直接拷贝文件

这张表不是在说 CMS 不好,而是指出两种路径的设计取舍。CMS 适合需要复杂权限管理、多人在线编辑、运营审核流的企业场景。Content-as-Code 适合个人创作者、小团队、技术内容为主的博客和文档站——这类场景的痛点往往不是「协作不够方便」,而是「内容被锁在平台里」。

完整工作流

一个典型的 Local-first AI 写作工作流分五个阶段:

流程图画布 · 115%
Mermaid 流程图加载中...

阶段一:创建文章

content/articles/<category>/ 下新建 Markdown 文件,用 frontmatter 声明元信息:

---
title: 我的文章标题
summary: 一句话摘要,用于列表页展示
createdAt: 2026-06-25
updatedAt: 2026-06-25
publishedAt: 2026-06-25
tags: [标签A, 标签B]
seoTitle: SEO 标题(可与 title 不同)
seoDescription: SEO 描述,搜索引擎摘要用
---

frontmatter 的作用不只是「文件头信息」。它是构建流程的输入——静态站点生成器根据它生成列表页、sitemap、RSS feed、JSON-LD 结构化数据。写对 frontmatter,后续的自动化才能跑通。

阶段二:AI 辅助写作

这是 Local-first 区别于传统工作流的关键环节。AI 辅助有三种接入方式,各有取舍:

方式工具示例延迟隐私性质量成本
本地 LLMOllama + Llama 3完全本地中等一次性硬件
云端 APIClaude API / OpenAI API数据离开本机按 token 计费
平台内置Notion AI / Jasper受平台条款约束订阅制

我日常用的是混合策略:日常补全和段落扩写走本地 Ollama(跑 Llama 3 或 Qwen 2.5),需要高质量重写或处理复杂逻辑时切到 Claude API。本地模型的速度够用于逐句补全,云端模型的推理能力在处理结构性修改时更可靠。

本地 LLM 的接入方式很简单——Ollama 装好后,一行命令就能启动:

# 安装并启动 Ollama
ollama serve
 
# 拉取模型
ollama pull llama3:8b
 
# 命令行交互测试
ollama run llama3:8b "帮我扩写这段关于版本控制的内容"

在编辑器里,可以通过 Continue.dev、Copilot 兼容层或自定义脚本对接 Ollama 的 API(http://localhost:11434/api/generate)。这样写作时 AI 补全全程在本地完成,内容不会离开你的机器。

阶段三:人工审稿

AI 生成的内容必须经过人工审查。我在审稿时关注几件事:

  • 事实准确性:LLM 会编造数据、引用不存在的论文。所有具体数字和引用必须逐一核实。
  • 逻辑连贯性:AI 扩写的段落看起来通顺,但仔细读经常有「每句话都对,连起来不对」的问题。
  • 术语一致性:同一篇文章里,同一个概念应该用同一个词。AI 会在「工作流」「流程」「workflow」之间随意切换。
  • 语气统一性:AI 续写部分的语气往往和原文有微妙差异——更正式、更圆滑、更「AI 味」。需要手动调回来。

审稿的过程本身也在 Git 里进行。每次有意义的修改做一个 commit,commit message 说明改了什么、为什么改。这样做的好处是:三个月后回头看,你能精确知道某段话是什么时候、因为什么原因被改成了现在的样子。

# 典型的写作过程 commit 记录
git log --oneline content/articles/ai-tools/local-first-ai-writing-workflow.md
 
# a3f2c1d 修正 Ollama 版本号,确认与官方文档一致
# b7e4a09 重写「AI 辅助写作」章节,补充混合策略说明
# c2d8f56 扩写「审稿」章节,增加术语一致性检查
# d1a9e23 完成初稿,提交 review

阶段四:构建验证

文章写好后,跑一遍构建流程确认没有问题:

# 类型检查(frontmatter 是否符合 schema)
pnpm content:check
 
# 本地预览(确认渲染正确)
pnpm dev
 
# 完整构建(包含所有页面的静态生成)
pnpm build

构建流程能发现的问题包括:frontmatter 字段缺失或类型错误、Markdown 语法错误(未闭合的代码块、错误的链接引用)、图片路径不存在、内部链接指向已删除的页面。这些错误如果漏到线上,轻则影响阅读体验,重则导致页面 404。

阶段五:发布上线

构建通过后,push 到远端仓库,CI/CD 自动完成部署:

# .github/workflows/deploy.yml 简化版
name: Deploy
on:
  push:
    branches: [main]
    paths:
      - 'content/**'
      - 'apps/web/**'
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - run: pnpm install
      - run: pnpm build
      - run: pnpm deploy:production

从写完最后一个字到内容上线,中间不需要打开任何 CMS 后台、不需要手动点「发布」。整个链路是:git push → CI 构建 → 静态生成 → CDN 部署。

案例:三种不同规模的实践

案例一:个人技术博客

一个人维护的技术博客是最典型的场景。我目前的博客仓库结构大致是这样:

content/
  articles/
    ai-tools/
      local-first-ai-writing-workflow.md
    engineering/
      ...
  pages/
    about.md
apps/
  web/
    src/
      app/
        [locale]/
          articles/
            [slug]/page.tsx

写作时用的工具组合是 VS Code + Continue.dev(对接 Ollama)+ Git。Continue.dev 在编辑器里提供行内补全,我按 Tab 接受或 Esc 拒绝。需要大幅改写时,打开终端用 ollama run 和模型对话,把结果手动贴回来。

这套方案跑了半年多,最明显的感受是「写作焦虑降低了」。以前在 Notion 里写,总担心平台哪天挂了或者改了导出格式。现在所有内容都是 .md 文件,存在 Git 仓库里,最坏的情况也就是换个编辑器——文件本身不会丢失。

案例二:小团队的内容协作

三个人的技术团队,需要写产品文档、发版说明和技术博客。他们用的方式是:

  • 所有内容在同一个 Git 仓库,按类型分目录
  • 每个人在自己的分支上写,写完后提 PR
  • 另一个人做 review,重点看技术准确性和语气一致性
  • 合并到 main 后自动部署到文档站

这里 Git 的 PR review 机制替代了传统 CMS 的「审核流」。好处是 review 过程本身也有版本记录——谁在什么时候提了什么修改意见、作者是否采纳,全部可追溯。

协作需求CMS 方案Git 方案
多人同时编辑原生支持分支并行,merge 时解决冲突
审核流程平台角色权限PR review + required approval
修改历史平台日志(有限)完整 commit 历史
回滚操作平台「恢复上一版本」git revert
非技术人员参与友好(Web 编辑器)需要基本 Git 知识

最后一点是 Git 方案的真实短板。如果团队有非技术背景的运营或市场同事频繁参与内容编辑,纯 Git 工作流的学习成本不低。务实的做法是:核心创作者用 Git 流程,非技术协作者通过 PR 评论或直接在 CMS 里操作(如果有混合架构的话)。

案例三:内容仓库 + 本地知识库

更进阶的用法是把内容仓库同时当作本地知识库来用。具体做法是:

  1. 用 Obsidian 打开内容仓库的 content/ 目录作为 vault
  2. 所有文章之间的引用关系用 Obsidian 的 [[]] 双链标记
  3. 本地跑一个 RAG 索引(比如用 LlamaIndex 或 LangChain),把所有 Markdown 文件向量化
  4. 写新文章时,先查询本地知识库看有没有相关内容可以复用或引用
# 本地 RAG 索引构建(简化示例)
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
 
# 读取 content 目录下所有 Markdown 文件
documents = SimpleDirectoryReader("content/articles").load_data()
 
# 构建向量索引(完全在本地完成)
index = VectorStoreIndex.from_documents(documents)
 
# 写新文章时查询相关内容
query_engine = index.as_query_engine()
response = query_engine.query(
    "我之前写过哪些关于 local-first 的内容?"
)
print(response)

这种方式的妙处在于:你的知识库是私有的、本地的、可版本控制的。AI 辅助写作时参考的是你自己的历史内容,而不是一个通用模型的训练数据。

工具选型对比

Local-first 写作工作流的工具链有几类选择,下面是我实际用过或调研过的对比:

Markdown 编辑器

编辑器本地 LLM 集成Git 集成价格适合场景
VS Code + Continue.dev原生支持 Ollama原生 Git + GitHub PR免费技术内容、需要代码块
Obsidian + Smart Connections插件支持 Ollama通过 obsidian-git 插件免费(个人)知识图谱、双链笔记
Typora不支持不支持$14.99 买断纯写作体验、不折腾
Cursor原生 AI(可选本地)原生 Git$20/月AI 辅助重度用户

本地 LLM 运行时

运行时安装难度GPU 要求模型支持适合场景
Ollama低(一行命令)可选Llama、Qwen、Mistral 等日常写作补全
llama.cpp中(需编译)可选(Vulkan)GGUF 格式模型追求性能和自定义
LM Studio低(GUI 安装)可选GGUF 格式模型不想碰命令行的用户

静态站点生成器

生成器框架内容集合国际化适合场景
Next.js (app router)Reactcontentlayer / fumadocsnext-intl全栈应用 + 内容站
Astro任意content collections内置内容为主的站点
DocusaurusReactdocs 目录内置技术文档
Hugo无(Go 模板)content 目录内置追求构建速度

隐私与数据安全的实际考量

Local-first 的一个核心卖点是隐私,但隐私不是一个二元状态,而是一系列权衡。

本地 LLM 推理:内容完全不离开你的机器。这是隐私性最强的方案,但代价是模型质量受限于硬件。8B 参数的模型在消费级 GPU 上跑补全没问题,但要它做长文逻辑重写,效果不如 Claude 或 GPT-4 级别的云端模型。

云端 API 调用:内容会发送到 API 提供商的服务器。主流提供商(Anthropic、OpenAI)的隐私条款通常承诺不用 API 数据训练模型,但数据确实在传输和临时存储过程中经过了第三方系统。如果对内容敏感度有要求(比如涉及未公开的产品信息、客户数据),应该优先用本地模型处理。

混合策略:脱敏后的内容用云端 API,敏感内容用本地模型。实际操作中,我会在发给云端 API 之前,把具体的人名、公司名、内部指标替换成占位符,等结果回来后再还原。

# 简单的内容脱敏示例
import re
 
SENSITIVE_PATTERNS = {
    r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b': '[EMAIL]',
    r'\b\d{3}-\d{2}-\d{4}\b': '[SSN]',
    r'内部项目代号\s*\S+': '内部项目代号 [REDACTED]',
}
 
def desensitize(text: str) -> str:
    for pattern, replacement in SENSITIVE_PATTERNS.items():
        text = re.sub(pattern, replacement, text)
    return text
 
# 发送到云端 API 前先脱敏
safe_text = desensitize(original_text)
# response = call_cloud_api(safe_text)
# final_text = restore_placeholders(response, original_text)

这不是完美的方案——真正的隐私需要端到端的本地处理。但在模型质量和隐私之间,混合策略是一个务实的折中。

什么时候不该用 Local-first

说完了好处,也得说局限。以下场景 Local-first 不是最优解:

  • 多人实时协作:如果团队需要多人同时编辑同一篇文档、实时看到对方的修改,Google Docs / Notion 的协作体验远好于 Git 分支 + merge。
  • 非技术协作者:运营、市场同事如果不熟悉 Git,强制他们用命令行操作会增加摩擦。可以考虑混合方案——技术作者用 Git,非技术作者用 CMS,通过 API 同步。
  • 高频内容更新:新闻类、社交媒体类内容需要快速发布和多平台分发,CMS + 多通道发布工具更合适。
  • 复杂权限管理:需要多级审批、角色区分(作者、编辑、审核者、发布者)的场景,CMS 的权限体系更成熟。

Local-first 不是银弹,它是一种针对特定场景的优化——个人创作者或小团队,以技术内容为主,重视数据主权和版本控制,愿意投入一定学习成本换取长期可控性。

开始之前的检查清单

如果你打算尝试这套工作流,下面是我踩过的坑总结出来的检查项。

环境搭建

  • Git 已安装,仓库已初始化,.gitignore 排除了不必要的文件(node_modules/.DS_Store、构建产物)
  • Markdown lint 工具已配置(markdownlintbiome 的 Markdown 规则),并接入 pre-commit hook
  • 本地 dev server 能正常启动,可以实时预览 Markdown 渲染效果
  • frontmatter schema 已定义(用 Zod、JSON Schema 或 contentlayer 的 defineCollection),构建时会校验

AI 辅助配置

  • Ollama 已安装,至少拉取了一个写作辅助模型(推荐 Llama 3 8B 或 Qwen 2.5 7B 起步)
  • 编辑器的 AI 补全已对接 Ollama API(Continue.dev、Copilot 兼容层或自定义脚本)
  • 云端 API 的 fallback 已配置——本地模型效果不够时能一键切换
  • 敏感内容处理流程已明确:哪些内容走本地、哪些可以走云端、脱敏规则是什么

内容规范

  • 文章目录结构已确定:content/articles/<category>/ 的 category 列表已定义
  • frontmatter 必填字段已明确:至少包括 title、summary、createdAt、tags
  • 图片存储策略已确定:放 public/images/ 还是 CDN,路径引用方式统一
  • 内部链接使用相对路径还是绝对路径已约定,构建时会检查死链

发布流程

  • CI/CD 已配置:push 到 main 后自动构建和部署
  • 构建验证包含 frontmatter 校验、Markdown lint、死链检查、图片路径检查
  • 预览环境已配置:PR 阶段可以预览渲染效果(Vercel Preview、Netlify Deploy Preview 等)
  • 回滚方案已确认:出问题时可以 git revert + 重新部署

写在最后

Local-first 的 AI 写作工作流,本质上是用软件工程的思路管理内容创作。Markdown 是纯文本格式,Git 是版本控制系统,构建流程是质量关卡,本地 LLM 是隐私友好的 AI 助手。这些工具都不是新发明,但组合在一起形成了一条和「依赖云端 SaaS」不同的路径。

这条路不适合所有人,但对于重视数据主权、习惯命令行工作流、以技术内容为主的创作者来说,它提供了一些云端工具给不了的东西:确定性、可控性和长期稳定性。你的文章不会因为你订阅的平台停运而丢失,不会因为平台改了算法而改变展示方式,不会因为某次 UI 更新而找不到某个功能。

先让内容跑起来,再决定是否需要更重的后台流程。这是我在原文里写的一句话,现在看来依然成立。Local-first 不是排斥 CMS,而是给内容创作一个轻量入口——一个你能完全掌控的入口。

参考资料

评论 0

0 / 1000