Local-first 的 AI 内容写作工作流
一篇草稿引发的信任危机
我在某个云端 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 把同样的工程化思路扩展到博客、教程、案例分析、产品更新、内部复盘这些「叙述性」内容。
两者的共同点是对工具链的要求:
| 维度 | 传统 CMS | Content-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 写作工作流分五个阶段:
阶段一:创建文章
在 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 辅助有三种接入方式,各有取舍:
| 方式 | 工具示例 | 延迟 | 隐私性 | 质量 | 成本 |
|---|---|---|---|---|---|
| 本地 LLM | Ollama + Llama 3 | 中 | 完全本地 | 中等 | 一次性硬件 |
| 云端 API | Claude 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 里操作(如果有混合架构的话)。
案例三:内容仓库 + 本地知识库
更进阶的用法是把内容仓库同时当作本地知识库来用。具体做法是:
- 用 Obsidian 打开内容仓库的
content/目录作为 vault - 所有文章之间的引用关系用 Obsidian 的
[[]]双链标记 - 本地跑一个 RAG 索引(比如用 LlamaIndex 或 LangChain),把所有 Markdown 文件向量化
- 写新文章时,先查询本地知识库看有没有相关内容可以复用或引用
# 本地 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) | React | contentlayer / fumadocs | next-intl | 全栈应用 + 内容站 |
| Astro | 任意 | content collections | 内置 | 内容为主的站点 |
| Docusaurus | React | docs 目录 | 内置 | 技术文档 |
| 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 工具已配置(
markdownlint或biome的 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,而是给内容创作一个轻量入口——一个你能完全掌控的入口。
参考资料
- The Definitive Guide to Local-First AI (2026) — SitePoint
- Why Local AI Writing Feels Better — Typeahead
- Content as Code: How to Automate Your Writing Workflow — SteakHouse Blog
- A Local-first LLM Knowledge Base with Built-in Automation — Medium
- Ollama — GitHub
- Ollama Setup 2026 | Local LLM Guide — SitePoint
- WritingTools: System-wide Local AI Writing — GitHub
- How to Write LLM-friendly Documentation — Fern