基于Stigmergy的团队LLM wiki协作机制与FastAPI工程实践 Stigmergy 听起来像一个冷门词但它可能是团队协作型 LLM wiki 最值得借鉴的设计原则。Andrej Karpathy 带动的 “LLM wiki” 讨论通常指向一种个人化知识管理范式一个人借助大语言模型持续提问、修订、链接页面把零散笔记变成持续生长的维基。问题在于知识一旦需要多人一起维护这一个人玩得很转的模式会立刻撞上冲突、上下文丢失和信任问题。Stigmergy 恰好提供了一种不需要实时沟通、不依赖某个“主编”的协作机制每个个体修改共享环境后续个体根据环境的当前状态继续工作。这篇内容会围绕一个可运行的工程原型解释如何把 Stigmergy 的原则落地成面向团队的 LLM wiki。1. Stigmergy 是什么为什么团队 LLM wiki 需要它1.1 Stigmergy 的原始模型个体留下环境痕迹群体迭代出结构Stigmergy 这个词源自生物学最常被引用的例子是白蚁筑巢。白蚁个体并不掌握“巢穴设计图纸”也不会开会讨论下一步怎么干。每只白蚁做的最多的事情是在已有泥塔上放下一颗泥球或者释放一点信息素。其他白蚁看到环境发生的改变后会继续在同样的位置加工泥塔越垒越高最终形成复杂的巢穴结构。这个机制有三个关键要素环境状态可观察、个体行为会改变环境状态、后续个体根据新的环境状态行动。信息和意图不需要通过面对面交流传递而是沉淀在环境里。软件工程中其实到处都有 StigmergyGit 提交历史、Pull Request 上的评论、CI 状态、代码里的 TODO 注释都是环境痕迹。团队成员不直接“同步大脑”而是通过对共享工件的修改来协作。1.2 Karpathy 式单人 LLM wiki 的核心特征所谓 Karpathy 式的 LLM wiki可以理解成一种由大语言模型辅助维护的个人知识库。一个人把一篇篇笔记、文章、项目文档拆成相互链接的页面用 LLM 来补充内容、调整结构、回答“这篇页面上一次写到这里接下来应该补什么”。页面成为长期记忆单元对话上下文只是短期工作台。这种模式对单个人非常有效因为所有知识状态都在同一个大脑里。用户不需要向外部解释背景不需要说明自己为什么要改一段话也不需要担心另一个人会误解页面上的某个措辞。LLM 只需要根据当前页面内容和一次对话指令就能生成一版合理的修订稿。但团队环境不具备这个前提。团队没有共享大脑每个人的经验、假设、上下文都不同。如果仍然按单人模式操作让多个成员各自和 LLM 对话再手工粘贴到同一个目录里结果几乎必然出现三类问题同一段知识被改成多个版本、页面之间互相矛盾、没人知道某个结论是怎么得出的。1.3 从一个人的知识库扩展到团队问题立刻出现把单人 LLM wiki 复制成团队版第一个直接问题是并发覆盖。两个人同时基于旧版本编辑后提交的人覆盖先提交的人。第二个问题是知识上下文丢失。A 写页面时知道自己为什么选择某个方案但 B 和 LLM 看不到这层背景修订时可能会把关键信息删掉。第三个问题是 LLM 的幻觉会被错误地当作权威内容。单人场景下用户还能凭自己对知识的掌握判断是否正确多人协作时编辑者很可能并不熟悉页面涉及的专业领域。这些问题靠“加一个登录页面”“加一个编辑锁”是解决不了的因为问题本质上是协作模型没有设计好。系统需要让每个人和每个 Agent 都能看到当前知识状态、知道谁在哪些地方留过疑问、能追溯每一次修改的原因。1.4 为什么 Stigmergy 恰好是解法Stigmergy 的启示是不要试图用实时通信去同步每个人而是把知识库本身变成共享环境。页面是环境版本历史是记录评论、TODO、QUESTION、CONFLICT 标记就是环境中的痕迹。人或者 LLM Agent 不需要知道其他协作者在做什么只需要查看当前页面上有哪些未解决的标记基于当前版本继续贡献。这样先修改环境的人通过“痕迹”影响后来者后来者又留下新痕迹。协作过程不需要强依赖记忆也不需要一个中心协调者。LLM 在这个模型里的角色也从“个人写作助手”变成了“知识维护工人”它读取当前页面、读取未解决标记、检索相关上下文生成一版增量改动最后由人确认发布。下面的表格可以更直观地对比单人 LLM wiki 和团队 Stigmergy Wiki。维度单人 LLM wiki团队 Stigmergy Wiki记忆来源个人上下文 本地文件共享页面 修订历史 痕迹标记协作方式自己提问、自己判断异步通过页面和标记协作冲突处理无冲突或手动覆盖版本对比 Stigmergy 标记LLM 角色个人写作助手知识维护工人参与异步协作核心风险知识丢失、维护意愿覆盖、矛盾、幻觉污染、权限越界2. 团队版 LLM wiki 的数据结构和协作流程2.1 三个核心实体Page、Revision、Trace在实现层面团队版 LLM wiki 至少要包含三个核心实体。Page 是当前知识状态。它有标题、正文、更新人、更新时间以及一个用于乐观锁的版本号。Page 不保存历史只保存当前有效内容。Revision 是每一次正式发布的快照。每次页面更新都会产生一条新 Revision记录版本号、作者、改动内容和备注。Revision 让系统可以回答“这个页面是怎么一步步变成现在这样的”这是 Stigmergy 最基础的一层痕迹。Trace 是页面上的协作痕迹。它可以是一条 QUESTION比如“这里的幂等键为什么不用订单号”也可以是一条 CONFLICT比如“A 方案和 B 方案在支付回调场景下矛盾”还可以是一条 CONTEXT比如“这段设计是基于银行渠道的 T1 结算周期”。Trace 必须绑定页面版本避免后来者用旧标记去干扰新内容。Pages、Revisions、Traces 三者构成了一个可追溯的协作环境。LLM 在生成修订建议时不应该只看页面正文还应该读取当前未解决的 Trace否则它不理解为什么页面会对某些问题保持特定写法。2.2 一套支撑检索的向量索引团队知识库通常会持续增长页面之间会互相引用LLM 单次输入又有上下文窗口限制。所以系统需要一套向量索引按语义相似度检索相关页面和段落。具体做法是页面每次发布后把内容切成若干 chunk每个 chunk 通过 Embedding 模型转成向量存入向量索引。当 LLM 要修订某个页面时系统用“页面标题 用户指令 未解决标记”作为查询向量检索最相关的几个历史片段和关联页面再拼接进 Prompt。向量索引解决的核心问题不是“找全所有资料”而是“在有限上下文内找到最可能影响本次修改的知识片段”。生产环境可以接入 Qdrant、Weaviate 或 PostgreSQL 的 pgvector学习环境可以用 SQLite 加一个简单的 cosine 相似度计算先跑通链路。2.3 协作闭环怎么走通一套基于 Stigmergy 的协作流程可以设计成六个步骤。团队成员创建或编辑页面系统生成新 Revision。其他成员或 LLM Agent 在页面上添加 Trace 标记比如提出问题、补充背景、标记冲突。任一成员发起 LLM review系统收集当前页面内容、未解决 Trace、相关页面片段。LLM 生成一版修订草案 draft不直接覆盖当前页面。人工确认 draft并通过携带 expected_version 的发布接口更新页面。页面更新后向量索引同步刷新未解决 Trace 保留解决后的 Trace 标记为 resolved。第 5 步是关键设计。不要让 LLM 拥有直接写页面的权限至少不要让它在没有人确认的情况下覆盖线上内容。人的确认是防止幻觉污染的兜底机制。2.4 技术选型和初始目录学习环境可以使用 FastAPI 提供 APISQLite 存储结构化数据sentence-transformers 或兼容 OpenAI 协议的 Embedding 接口做向量化Streamlit 或纯 curl 做验证。生产环境建议使用 PostgreSQL pgvector、独立的向量数据库、统一的模型网关以及成熟的身份认证系统。一个最小项目的目录可以这样组织。stigmergy-wiki/ ├── app/ │ ├── main.py │ ├── db.py │ ├── llm.py │ ├── vector_index.py │ ├── stigmergy.py │ └── config.py ├── data/ ├── config.yaml └── requirements.txt接下来从代码层面实现这个原型。3. 用 FastAPI 和 SQLite 搭建最小可运行版本3.1 初始化依赖和配置先安装必要的依赖。requirements.txt 示例如下。fastapi0.110 uvicorn[standard]0.29 pydantic2.6 PyYAML6.0 openai1.12 sentence-transformers2.2sentence-transformers 用于本地向量化openai 库用于调用兼容 OpenAI 协议的 REST 接口。实际项目建议先把依赖版本锁定不要使用这种宽松版本范围部署到生产环境。配置文件 config.yaml 可以这样设计。database: path: data/wiki.db embedding: provider: local model: BAAI/bge-small-zh-v1.5 chunk_size: 800 chunk_overlap: 100 llm: base_url: http://localhost:11434/v1 api_key: ollama model: qwen2.5:7b temperature: 0.2这里的 LLM 服务使用本地 Ollama 的 OpenAI 兼容地址也可以换成团队自己的模型网关地址。不要把 API Key 写死在配置文件里推荐通过环境变量注入。3.2 建立数据库表最小原型需要四张表pages、revisions、traces、page_chunks。SQLite DDL 如下。CREATE TABLE IF NOT EXISTS pages ( id TEXT PRIMARY KEY, title TEXT NOT NULL, content TEXT NOT NULL, version INTEGER NOT NULL DEFAULT 1, updated_by TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS revisions ( id INTEGER PRIMARY KEY AUTOINCREMENT, page_id TEXT NOT NULL, version INTEGER NOT NULL, content TEXT NOT NULL, diff TEXT NOT NULL DEFAULT , author TEXT NOT NULL, note TEXT NOT NULL DEFAULT , created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS traces ( id INTEGER PRIMARY KEY AUTOINCREMENT, page_id TEXT NOT NULL, page_version INTEGER NOT NULL, marker TEXT NOT NULL, message TEXT NOT NULL, author TEXT NOT NULL, resolved INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS page_chunks ( id INTEGER PRIMARY KEY AUTOINCREMENT, page_id TEXT NOT NULL, page_version INTEGER NOT NULL, chunk_index INTEGER NOT NULL, content TEXT NOT NULL, embedding TEXT NOT NULL );Trace 表里的 marker 可以枚举为QUESTION、CONFLICT、CONTEXT、TODO。resolved 字段用于标记是否已经关闭。page_chunks 表在最小原型中把向量以 JSON 字符串存入 SQLite方便验证逻辑生产环境应当替换为真正的向量数据库。3.3 实现页面、修订、痕迹 API下面用 FastAPI 实现页面创建、查询、痕迹添加和发布接口。核心组件是 sqlite3 存储层。先在 db.py 里实现连接和建表。import sqlite3 from pathlib import Path DB_PATH Path(__file__).resolve().parent.parent / data / wiki.db SCHEMA -- 上面定义的 DDL def get_conn(): DB_PATH.parent.mkdir(parentsTrue, exist_okTrue) conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row return conn def init_db(): conn get_conn() try: conn.executescript(SCHEMA) conn.commit() finally: conn.close()然后在 main.py 中实现 API。注意发布接口必须使用乐观锁避免并发覆盖。import uuid from datetime import datetime, timezone from typing import List, Optional from fastapi import FastAPI, HTTPException from pydantic import BaseModel from db import get_conn, init_db app FastAPI(titleStigmergy Wiki) class PageCreate(BaseModel): title: str content: str author: str class TraceCreate(BaseModel): marker: str message: str author: str class ReviewRequest(BaseModel): instruction: str class PublishRequest(BaseModel): content: str expected_version: int author: str note: str app.on_event(startup) def on_startup(): init_db() def now(): return datetime.now(timezone.utc).isoformat() app.post(/pages) def create_page(payload: PageCreate): page_id uuid.uuid4().hex ts now() version 1 conn get_conn() try: conn.execute( INSERT INTO pages (id, title, content, version, updated_by, updated_at) VALUES (?, ?, ?, ?, ?, ?), (page_id, payload.title, payload.content, version, payload.author, ts), ) conn.execute( INSERT INTO revisions (page_id, version, content, diff, author, note, created_at) VALUES (?, ?, ?, ?, ?, ?, ?), (page_id, version, payload.content, , payload.author, initial, ts), ) conn.commit() finally: conn.close() return {id: page_id, version: version} app.get(/pages/{page_id}) def get_page(page_id: str): conn get_conn() try: row conn.execute(SELECT * FROM pages WHERE id ?, (page_id,)).fetchone() finally: conn.close() if row is None: raise HTTPException(status_code404, detailpage not found) return dict(row) app.post(/pages/{page_id}/traces) def add_trace(page_id: str, payload: TraceCreate): conn get_conn() try: page conn.execute(SELECT * FROM pages WHERE id ?, (page_id,)).fetchone() if page is None: raise HTTPException(status_code404, detailpage not found) ts now() conn.execute( INSERT INTO traces (page_id, page_version, marker, message, author, resolved, created_at) VALUES (?, ?, ?, ?, ?, 0, ?), (page_id, page[version], payload.marker, payload.message, payload.author, ts), ) conn.commit() trace_id conn.execute(SELECT last_insert_rowid() AS id).fetchone()[id] return {id: trace_id, page_version: page[version]} finally: conn.close() app.get(/pages/{page_id}/traces) def list_traces(page_id: str, unresolved_only: bool True): conn get_conn() try: if unresolved_only: rows conn.execute( SELECT * FROM traces WHERE page_id ? AND resolved 0 ORDER BY created_at, (page_id,), ).fetchall() else: rows conn.execute( SELECT * FROM traces WHERE page_id ? ORDER BY created_at, (page_id,), ).fetchall() finally: conn.close() return [dict(r) for r in rows]发布接口的关键在版本校验。app.put(/pages/{page_id}/publish) def publish_revision(page_id: str, payload: PublishRequest): conn get_conn() try: page conn.execute(SELECT * FROM pages WHERE id ?, (page_id,)).fetchone() if page is None: raise HTTPException(status_code404, detailpage not found) if page[version] ! payload.expected_version: raise HTTPException(status_code409, detailversion conflict) new_version page[version] 1 ts now() conn.execute( UPDATE pages SET content ?, version ?, updated_by ?, updated_at ? WHERE id ?, (payload.content, new_version, payload.author, ts, page_id), ) conn.execute( INSERT INTO revisions (page_id, version, content, diff, author, note, created_at) VALUES (?, ?, ?, ?, ?, ?, ?), (page_id, new_version, payload.content, , payload.author, payload.note, ts), ) conn.commit() return {page_id: page_id, new_version: new_version} except Exception: conn.rollback() raise finally: conn.close()3.4 实现 LLM 修订生成器LLM 修订生成器位于 llm.py。它负责读取当前页面、未解决 Trace、相关检索片段并构造 Prompt。import os from openai import OpenAI client OpenAI( base_urlos.getenv(LLM_BASE_URL, http://localhost:11434/v1), api_keyos.getenv(LLM_API_KEY, ollama), ) def collect_traces_text(traces): lines [] for t in traces: lines.append(f- [{t[marker]}] {t[message]}{t[author]}页 v{t[page_version]}) return \n.join(lines) def build_review_prompt(page, traces_text, related_text, instruction): return f 你是一个团队知识库维护助手。你的任务是基于当前页面内容、协作痕迹和相关资料生成一版修订稿。 要求 1. 只输出修订后的完整页面内容不要输出解释。 2. 尽可能保留原有有效信息。 3. 如果痕迹中包含问题或冲突在修订稿中尽量回应。 4. 不要编造外部事实。 当前页面标题{page[title]} 当前页面内容 {page[content]} 未解决协作痕迹 {traces_text} 相关检索片段 {related_text} 用户修订指令 {instruction} def generate_draft(page, traces, related_text, instruction): traces_text collect_traces_text(traces) prompt build_review_prompt(page, traces_text, related_text, instruction) response client.chat.completions.create( modelos.getenv(LLM_MODEL, qwen2.5:7b), messages[{role: user, content: prompt}], temperature0.2, ) return response.choices[0].message.content这里将 temperature 设置得比较低是为了让 LLM 更倾向保守修改而不是天马行空地重写全文。实际项目还应该在调用中增加超时设置、重试策略和成本统计。3.5 实现向量检索接口向量索引模块负责把页面内容切分、向量化并提供语义搜索接口。最小实现可以用 sentence-transformers 和 cosine 相似度。import json import math from sentence_transformers import SentenceTransformer _model None def get_model(): global _model if _model is None: _model SentenceTransformer(BAAI/bge-small-zh-v1.5) return _model def embed_texts(texts): model get_model() vectors model.encode(texts, normalize_embeddingsTrue) return vectors.tolist() def cosine(a, b): return sum(x * y for x, y in zip(a, b)) def add_chunks(conn, page_id, page_version, content, chunk_size800, overlap100): chunks [] start 0 while start len(content): chunks.append(content[start:start chunk_size]) start chunk_size - overlap vectors embed_texts(chunks) for idx, (chunk, vec) in enumerate(zip(chunks, vectors)): conn.execute( INSERT INTO page_chunks (page_id, page_version, chunk_index, content, embedding) VALUES (?, ?, ?, ?, ?), (page_id, page_version, idx, chunk, json.dumps(vec)), ) def search(conn, query, top_k3): qvec embed_texts([query])[0] rows conn.execute(SELECT * FROM page_chunks).fetchall() scored [] for row in rows: vec json.loads(row[embedding]) score cosine(qvec, vec) scored.append((score, row)) scored.sort(keylambda x: x[0], reverseTrue) return [dict(row) for _, row in scored[:top_k]]学习环境直接全表扫描即可。生产环境数据量增大后建议换成 pgvector 或 Qdrant并让查询命中索引。4. 嵌入、标记和冲突控制细节决定 LLM 协作质量4.1 页面分块和向量化时的关键参数页面内容不是越大越好。过大的 chunk 会让向量语义被稀释过小的 chunk 又会丢失上下文。常见参数建议如下。参数学习环境建议生产环境建议影响chunk_size500 到 800 字符500 到 1000 字符太大会语义模糊太小会上下文不足chunk_overlap80 到 150 字符100 到 200 字符避免段落边界丢失信息embedding model中文领域使用 bge-small按语种和效果测试选择模型不匹配会严重降低检索准确率检索 top_k2 到 33 到 5太大会把无关内容塞进 Prompt不要以为向量越多越好。实际项目中top_k 增加会直接增加 LLM 输入长度和成本也可能引入噪声。建议先做一次小规模评测记录每个查询命中的片段是否真的和问题相关。4.2 如何把 Stigmergy 标记转换成 PromptStigmergy 标记要发挥作用必须能被 LLM 准确理解。最简单的方式是把未解决的 Trace 格式化成结构化的文本放进 Prompt 中。例如页面上有这样

相关新闻

最新新闻

学生党用 AI 辅助学习的正确姿势:从“抄答案”转向“深度理解”

学生党用 AI 辅助学习的正确姿势:从“抄答案”转向“深度理解”

Q:学生党如何正确用 AI 辅助学习?怎样才能避免沦为“抄作业”的工具? A: 1.分项结论 ① 核心数据与指标参数: 效率对比:传统低效的“直接复制答案”学习法,期末复习及知识点留存率仅为 15%&a…

2026/8/28 8:19:48
蓝桥杯冲刺:真题解析心法与进制转换、交换瓶子、博弈问题实战

蓝桥杯冲刺:真题解析心法与进制转换、交换瓶子、博弈问题实战

1. 赛前冲刺的“真题”价值:不止是刷题 距离蓝桥杯比赛还有最后几天,很多同学的状态可能和我当年一样:题库刷了不少,但心里还是没底,总觉得还差点什么。这时候,最常见的做法就是疯狂找“真题”来刷&#xf…

2026/8/28 8:19:48
蓝桥杯国赛真题解析:最长公共子序列(LCS)在蓝肽子序列问题中的应用

蓝桥杯国赛真题解析:最长公共子序列(LCS)在蓝肽子序列问题中的应用

1. 项目概述:从“蓝肽子序列”看国赛动态规划命题逻辑 看到“蓝肽子序列”这个题目,很多参加过蓝桥杯国赛或者正在备赛的同学可能会心一笑,或者眉头一紧。这确实是2020年第十一届蓝桥杯软件类国赛(C/C/Java组)的一道经…

2026/8/28 8:19:48
PNG隐藏水印揭秘:用Python解析C2PA与GUID标识

PNG隐藏水印揭秘:用Python解析C2PA与GUID标识

最近看到一条很有意思的消息:有人在逆向 Windows 11 上的“画图”和“照片”应用时发现,微软为本地 AI 生成或编辑过的图片加入了不可见的 GUID 水印。也就是说,你用记事本、画图工具生成的 AI 图片,表面看起来和普通图片没有任何…

2026/8/28 8:19:48
Level 4自动驾驶系统设计47——L4 架构设计 0

Level 4自动驾驶系统设计47——L4 架构设计 0

第 9 章:L4 级“异构三芯片”失效可用架构设计 9.1 核心算力组分布式部署:主/从双片冗余 SoC 跨芯片张量并行与全时数据交叉校验 9.1.1 自动驾驶全栈并网下的单芯算力断层与冗余重构 当自动驾驶系统演进至 Level 4 级全栈并网大模型(VLA/端到端基座模型)时,前级感知融合…

2026/8/28 8:19:48
Python networkx邻接矩阵可视化:从矩阵构建到布局优化的完整指南

Python networkx邻接矩阵可视化:从矩阵构建到布局优化的完整指南

1. 从邻接矩阵到可视化网络:一个被低估的起点在数据分析和算法研究的路上,我们经常和“图”打交道。无论是社交网络里的好友关系、知识图谱里的概念链接,还是交通网络里的站点连接,本质上都可以抽象成点和边的集合。而邻接矩阵&am…

2026/8/28 8:14:48