第1讲项目初始化:Codex 连上 TaoToken 后能跑通 PieceTable 文本模型
2026/9/16 9:30:38 网站建设 项目流程

实时协同编辑引擎第1讲的项目初始化,真正的坎在 PieceTable 的边界:插入点落在片段中间时要不要拆成两半,删除跨多个片段时头尾怎么保留,撤销之后版本号该加还是该减。手写这些逻辑很容易栽在细节上。这一讲我沿用原文的 collaborative-editor 骨架,但把 Codex 经 TaoToken 接入项目,让它负责生成与审查 PieceTable、Document、HistoryManager 这套文本模型,最终用 tests/test_document.py 全绿来确认插入、删除、撤销、版本号都符合预期。TaoToken 在中间只做一件事:作为 Codex 的 API 接入通道,让本地大模型调用稳定可用,并不替代 PieceTable 本身的任何文本操作。

1. PieceTable 的边界问题,比项目骨架更值得先想清楚

1.1 先把 collaborative-editor 骨架和环境搭起来

原文的项目结构分五块:core 放文档引擎(document.py、operation.py、cursor.py、history.py),server 和 client 留给后面的 WebSocket 同步,tests 放单元测试,examples 放交互演示。第1讲真正会动的文件只有三个:core/document.pycore/history.pycore/cursor.py,再加tests/test_document.pyexamples/simple_editor.py

所以初始化时不必一次把所有依赖装全。网页服务器相关的 websockets、fastapi、uvicorn 可以等第2讲用到再补,现在只需要一个能跑 pytest 的 Python 环境:

mkdir collaborative-editor && cd collaborative-editor python -m venv venv source venv/bin/activate pip install -U pytest

这一步完成后,目录保持原文的core/server/client/tests/examples/五段结构即可。空目录先建出来,第1讲的代码主要落在core/document.pycore/history.py

1.2 数据结构选型:为什么选 PieceTable

原文对比了六种文本表示方案,选型逻辑直接决定第1讲代码怎么写。简单概括:

数据结构插入复杂度删除复杂度适用场景
Python 字符串O(n)O(n)小文本、一次性渲染
数组 ListO(n)O(n)简单编辑器
Gap BufferO(1)~O(n)O(1)~O(n)单光标编辑器,Emacs/Vim 采用
Piece TableO(1)O(1)频繁插入删除 + 操作日志,VS Code 采用
RopeO(log n)O(log n)大型文本文档
CRDT TreeO(log n)O(log n)多人实时协同

实时协同编辑器里,用户每一次按键都可能触发 insert 或 delete,而且每次操作都要能被记录成 Operation 供撤销和同步。Python 字符串每次插入都复制整个文本,短文档还能忍,长文档和多人协同场景会直接卡顿。Piece Table 不直接改文本,而是维护一个“片段表”,每个 Piece 指向某个缓冲区里的一段连续区域,插入删除只在表上做拆拼,所以复杂度稳定在 O(1),还能天然留下操作日志。第1讲选它做核心结构,后面第2讲做 Operational Transformation 时,这些 Operation 就是变换的基本单位。

2. Codex 要连 TaoToken:先创建 Key,再改 Codex 配置

2.1 在官网创建 YOUR_API_KEY

先打开 TaoToken 注册并创建 API Key。拿到 Key 后用YOUR_API_KEY这个占位符统一表示,不要把它写进代码仓库,也不要提交到 config.toml 里。

要分清两件事:官网页只负责注册账号、创建 Key、看模型广场、看调用用量;真正填进 Codex 的接口地址是https://taotoken.net/api,末尾不要带/v1。落地页和接口是两个地址,很多人把网页地址直接塞进工具,结果连半天都是 404。

2.2 在 ~/.codex/config.toml 写入 Base URL 和模型供应商

Codex 读取的是用户目录下的~/.codex/config.toml,在这里新增一个名为taotoken的 model provider:

# ~/.codex/config.toml model = "你的模型ID" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY"

env_key表示 Codex 会从名为TAOTOKEN_API_KEY的环境变量里读取 Key,这样密钥不会落在配置文件里。base_urlhttps://taotoken.net/api,不追加/v1model字段写什么模型 ID,要以 TaoToken 模型广场当时列表为准,不要凭印象猜“最新版本号”,模型 ID 填错时 Codex 会直接报模型不存在,而不是给你一个可用的降级提示。

提示:官网落地页和接口地址不要混用。注册、建 Key、看用量去https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=;填进 Codex 的 Base URL 用https://taotoken.net/api

2.3 用一句“选型问题”验证连接

配置写好后,先用一条小请求验证 Codex 是否真的连上了 TaoToken:

export TAOTOKEN_API_KEY=YOUR_API_KEY codex exec "第1讲的文本模型里,PieceTable 和 Python 自带字符串相比,为什么更适合实时协同编辑?"

如果 Codex 能正常回答,说明 Key、模型 ID、Base URL 三样都对了。如果返回401,基本是TAOTOKEN_API_KEY没设置成功或 Key 复制不完整;如果返回模型相关的错误,就去模型广场重新查一遍 ID。不要一上来就跑大任务,先用这句话把链路探通。

3. 让 Codex 按第1讲写出 PieceTable,并在边界处多审几遍

3.1 生成核心类 BufferType、Piece、PieceTable

链路通了之后,让 Codex 在collaborative-editor里生成第1讲的文本模型核心代码:

cd collaborative-editor codex exec "请按实时协同编辑引擎第1讲的规范实现以下内容: 1. 在 core/document.py 中定义 BufferType、Piece、PieceTable。 2. PieceTable 需要支持 insert、delete、slice、to_string、char_length、_find_position、debug_info。 3. 在 core/history.py 中实现 HistoryManager,内部使用 undo_stack 与 redo_stack。 4. 在 core/document.py 中实现 Document,组合 PieceTable 和 HistoryManager,每次真实修改都递增 version。 5. 不要省略 insert 拆片段、delete 跨片段的边界处理。"

生成后重点审两类代码。BufferTypeORIGINALADDITION两个常量区分原始缓冲区与追加缓冲区,原始缓冲区只读,所有新插入文本一律追加到 addition_buffer;Piece是 frozen dataclass,字段包含 piece_id、buffer、offset、length。任何修改都不能直接改动 original_buffer 里的内容,这是 Piece Table 的根。

3.2 insert 拆片段是重灾区

"Hello, World!"为例,在 offset 6 处插入"Beautiful "后,正确结果是"Hello, Beautiful World!"。Piece Table 的处理顺序是:先把新文本追加到 addition_buffer,拿到 add_offset;再在片段表里定位到旧片段;最后把旧片段拆成 left_piece 和 right_piece,中间插入指向 addition_buffer 的新 Piece。

手写时最容易错的地方集中在三点:

  • offset_in_piece为 0 时不生成 left_piece,等于旧片段长度时不生成 right_piece,很多实现会在这两个边界上产生空 Piece。
  • 新 Piece 的 buffer 必须是ADDITION,如果复用旧片段的 ORIGINAL buffer,会破坏原始缓冲区只读语义。
  • 重组顺序必须是“旧片段左侧 + 新片段 + 旧片段右侧”,漏掉任何一段都会让文本内容对不上。

让 Codex 生成完这段后,直接让它对照"Heorld"在位置 2 插入"ll"的输出,预期是"Hello World"。如果 Codex 给出的实现把中间插入变成了覆盖或重复,说明它的片段拆分逻辑不合格,让它重写拆分部分,而不是继续往后加功能。

3.3 Document 串起 HistoryManager 和版本号

Document是完整文档引擎的门面,它组合了 PieceTable、HistoryManager、CursorManager。insert 和 delete 操作都要遵循同一套流程:先让 PieceTable 执行修改,如果返回的 Operation 不是 NOOP,就 push 进 HistoryManager,并把 version 加 1。undo 时从 HistoryManager 取 inverse 操作,再调用 PieceTable 应用;redo 则是取出原始操作继续应用。CursorManager 负责记录每个用户的光标位置和选区,第1讲只保证 position 字段在插入删除后不越界,真正的远端同步放到第2讲。

version 在这里不只是计数器,它后续要作为协同同步的参照系。所以验证时不要只盯着文本内容对不对,还要确认每次真实操作后 version 都严格递增。Codex 生成 Document 时如果跳过了 version 自增,pytest 里test_version_increment会立刻报红。

4. 跑 pytest 验证插入、删除、撤销、版本号

4.1 tests/test_document.py 全绿的判据

在项目根目录运行:

source venv/bin/activate python -m pytest tests/test_document.py -v

原文的测试用例覆盖了四组行为,全绿意味着以下场景全部成立:

  • 插入:空文档插入、开头插入、中间插入、结尾插入、连续多次插入后文本按顺序拼接。
  • 删除:删除开头、删除结尾、删除中间一个字符,删除后剩余文本正确拼接。
  • 撤销重做:连续两次 undo 后文本回到更早状态,再连续两次 redo 后文本完全恢复。
  • 版本号:每次 insert、delete、undo、redo 后 version 都严格加 1。

中间插入是最容易翻车的用例。像"Heorld"在位置 2 插入"ll",期望输出是"Hello World"。如果跑出来的结果是"HelloWorld""Heoll World",说明 insert 拆分片段时 left_piece 和 right_piece 的 offset 或 length 算错了,优先查_find_position返回的offset_in_piece是否正确。

4.2 pytest 红了就贴回给 Codex

不要急着改断言,也不要自己逐行猜。直接把 pytest 的完整输出贴回 Codex:

cd collaborative-editor codex exec "我运行 pytest tests/test_document.py -v 后失败,完整输出如下:…… 请判断是 insert 拆分逻辑、delete 尾部处理还是 HistoryManager 的 invert 有错,并直接给出修改后的完整代码。"

常见的失败形态有两个:报IndexError: Position out of range,说明_find_position在边界位置没有正确落到最后一个片段;undo 之后文本没变化,说明Operation.invert()得到的 position 或 deleted_text 与原始操作不匹配。把报错和失败断言一起贴回去,Codex 通常能定位到具体函数,重点检查它有没有改core/history.py里的栈逻辑。

4.3 用 simple_editor.py 走一遍交互式演示

pytest 全绿后,再跑examples/simple_editor.py

python examples/simple_editor.py

这个脚本会按“初始文本、插入、删除、撤销、再撤销、重做”的顺序打印每一步的文本和版本号。正常输出序列应该是:初始"Hello World!",插入"Beautiful "后变成"Hello Beautiful World!",删除后变回,撤销两次回到初始文本,重做又依次恢复。版本号会随着每一步真实操作递增。这里如果撤销和重做后的文本不一致,说明 HistoryManager 的 push 或 invert 方向反了,回看第3.3节的流程。

5. 测试全绿后,回 TaoToken 控制台对一次用量

5.1 在控制台核对本次调用是否记上账

刚才启动 Codex 时用的 Key 和模型 ID,会在 TaoToken 控制台形成对应的调用记录。测试跑完后打开 API Keys 页面 检查这次对话是否正常记账,确认没有出现“Key 有效但调用数为 0”的异常。如果后续要在浏览器里直接验证某个模型 ID 的回复质量,可以先在 模型对话 里用同一把 Key 发一条消息对比结果;要长期做第2讲这类多轮代码生成,再打开 Coding Plan 看套餐是否合适。

5.2 下一讲:OT 算法正好用上 Operation 和版本号

原文第2讲要进入 OT 算法,操作转换需要基于 position、version 和 Operation 本身做变换。第1讲里Operation.invert()已经为撤销打好了底,Document 的 version 也会成为冲突检测的参照。Codex 走 TaoToken 这条接入通道不需要改动,继续保留~/.codex/config.toml和环境变量即可。到时候直接在项目目录里让 Codex 生成转换函数和冲突测试,你负责把 pytest 结果拿回来核对。先把这一讲的绿点守住,第2讲就是在这些绿点之上做变换。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询