记录日期:2026-04-28
标签:AI 辅助开发 / 代码库交接 / Onboarding / Tech Graph / 文档工程化 / 防漂移 / 赛马对比 / RAG / Unified Chat / SSE 契约 / CI 门禁 / 多 Agent 协作
适合谁读:正在用 AI 辅助维护代码库的团队、被「AI 读不懂我的项目」困扰的开发者、想建立可复用 onboarding 流程的技术负责人
摘要
这篇文章源于一个真实问题:如何让新的 AI Agent(或新人)快速、准确地接手一个复杂代码库?
我围绕同一个后端项目做了三轮对比实验,比较了两种最近在项目中使用的极端策略——「全量深读代码」vs「先读架构图谱再抽样核验」。实验发现:两种策略各有明显短板,全量深读容易漏掉制度层约束(如接口清单、CI 门禁),索引导航容易缺失实现层细节(如行级锚点、踩坑点)。
最终我沉淀出一个混合工作流 Hybrid V1:先导航(架构图谱建立心智地图)→ 再深潜(代码/SQL/清单做真值核验)→ 过安检(门禁脚本防漂移)。这套流程最初是为「多 Agent 协作交接」设计的,但实践下来发现,它同样适用于人与人之间的代码库交接——核心都是「在有限上下文下,最大化信息密度和可审计性」。
文章包含可直接复用的:接口清单 JSON 格式、SSE 契约 JSON 格式、清单校验脚本(Python 正则提取)、行级锚点索引表模板、改动配方卡模板,以及小项目/老项目的简化策略。
一个真实的困境
你的项目越来越复杂,后端有几十个接口,前端有跨端契约,数据库有向量检索和全文搜索两套召回逻辑。某天你需要让一个新的 AI Agent(或者一位新同事)快速理解这个项目,你会怎么做?
方案一:把代码库丢给它,说「你自己读」。它可能会通读几千行代码,产出一份「实现讲义」——细节丰富,但容易漏掉关键边界,比如「新增接口必须同步更新清单文件,否则 CI 会挂」。
方案二:先让它读一份精心维护的技术图谱(架构图、流程图、接口索引),再抽样核验代码。它能快速建立心智地图,但面对「这里有个历史遗留的降级逻辑,第 670 行」这类细节时,可能只读了个开头就下了结论。
这就是我今天要聊的问题:没有银弹,但可以有更好的组合。
我做了什么:三轮实验,逐步收敛
为了找到「让 AI 接手代码库」的最佳路径,我围绕同一个后端项目做了三轮对比实验。每一轮都在修正前一轮的问题,最终沉淀出一个可复用的工作流。
第一轮:建立基线,发现「不可比」
我让两个 Agent 分别用两种极端策略产出交接文档:
- Agent A(全量深读):禁止读任何架构图谱,直接通读代码、SQL、测试文件。产出像一份「讲义」——行级锚点密集,适合直接改代码。
- Agent B(索引导航):强制先读架构图谱,再按图索骥抽样核验代码。产出像一份「地图」——边界清晰,适合快速定位。
结果让人哭笑不得:Agent A 自报耗时 47 秒,Agent B 自报 32 分钟。为什么差距能达到 40 倍?因为 Agent A 的「47 秒」只统计了「工具调用往返时间」——它把「读文件」当成瞬时的工具输出,没有计入 AI 实际阅读和理解内容的时间;而 Agent B 的「32 分钟」是把「从开始读到写完文档」的整个墙钟时间拆成了 t_graph(读图谱)+ t_read(读代码)+ t_synthesis(写文档)三段累加。两者统计的不是同一个东西。
token 消耗上更混乱:一个用「每字符 1.2 token」估算,另一个用「每 4 字符 1 token」估算。口径完全不一致,对比毫无意义。
收获:在让 AI 做对比之前,必须先统一「计量语言」。
第二轮:统一口径,发现一个反直觉结论
第二轮我强制双方使用同一套公式:代码按每行 12 token、文档按每行 10 token、中文输出按每 4 字符 1 token。
这次数据变得可读了:
| 维度 | Agent A(全量深读) | Agent B(索引导航) |
|---|---|---|
| Token 消耗 | ~75,000 | ~68,500(省 8.7%) |
| 自报耗时 | ~28 分钟 | ~36 分钟 |
| 产出风格 | 实现讲义,适合「准备动刀」 | 索引 + 契约,适合「长期维护」 |
反直觉的发现:读架构图谱确实更省 token(因为图谱内容比通读代码更浓缩),但并没有更快——读图谱的 8 分钟是刚性成本,叠加代码核验后,总时间反而更长。
收获:「省钱」和「省时」是两个独立的维度,不能混为一谈。如果你的目标是降低 AI 调用成本,图谱先行;如果你的目标是快速产出,需要进一步收敛核验范围。
第三轮:引入 KPI 权重,让 A 和 B 各补一块短板
第三轮我不再单纯比较成本,而是引入了一套加权 KPI。这套权重没有通用调研支撑,纯粹基于我团队的场景偏好:
易交接(40%)> 可靠性(35%)> 省钱(15%)> 省时(10%)
我的逻辑很简单:如果新人接手后三天就踩坑把 CI 搞挂了,省下来的 token 和时间毫无意义。所以「易交接」和「可靠性」排在前面,「省钱」和「省时」只是加分项。不同项目可以按自己的场景调整——比如 ToC 高频迭代项目可能把「省时」权重调高,ToB 稳定维护项目可能把「可靠性」再往上提。
同时做了一个关键补丁:让 Agent A 也能读取两份「门禁真值」文件——一份记录所有接口和 RPC 的清单(JSON 格式),另一份记录前端与后端之间的 SSE 事件契约(也是 JSON)。这相当于告诉它:「你不需要读完整张地图,但必须知道过安检的规矩。」
示例:接口清单 JSON(_manifest.json)
这是我仓库里真实使用的清单文件(节选):
{
"schema_version": "tech_graph_manifest_v1",
"endpoints": [
{"method": "GET", "path": "/api/py/health", "handler": "health"},
{"method": "POST", "path": "/api/py/chat", "handler": "chat"},
{"method": "POST", "path": "/api/py/unified/chat", "handler": "unified_chat_route"},
{"method": "POST", "path": "/api/py/unified/chat/stream", "handler": "unified_chat_stream_route"}
],
"supabase": {
"tables": ["documents", "code_chunks", "rag_conversation_logs"],
"rpc": ["match_documents", "keyword_documents", "refresh_documents_fts_tokens_for_paths"]
},
"env": ["SILICONFLOW_API_KEY", "NEXT_PUBLIC_SUPABASE_URL", "SUPABASE_SERVICE_ROLE_KEY"]
}
示例:SSE 事件契约 JSON(_contract_manifest.json)
{
"schema_version": "tech_graph_contract_manifest_v1",
"sse": {
"allowed_events": ["chain", "done"],
"chain": {
"data_keys": ["type", "ts", "step_id", "payload"],
"type_values": ["meta", "router.decision", "rag.sources", "sql.result", "assistant.message", "error"],
"payload_min_keys_by_type": {
"meta": ["run_id", "mode", "session_id"],
"rag.sources": {"payload_keys": ["sources", "retrieval"], "source_item_keys": ["id", "content", "score", "path"]},
"sql.result": ["sql", "columns", "rows", "truncated"]
}
},
"done": {"data_keys": ["ok", "mode", "run_id", "session_id", "request_id"]}
}
}
补丁之后,双方的差距大幅缩小:
- Agent A 补齐了「新增接口必须更新清单、跑门禁脚本」的闭环,不再是一个「只知道改代码」的莽夫。
- Agent B 依然更擅长发现「文档与代码不一致」的漂移(比如清单里写的接口名和代码里实际注册的不符),但在「行级锚点」和「踩坑细节」上仍然不如 Agent A 细腻。
收获:两种策略的短板可以互补——A 缺的是「制度层防漂移意识」,B 缺的是「实现层纵深」。与其二选一,不如组合。
最终方案:Hybrid V1,一个「先导航、再深潜、过安检」的工作流
三轮实验的结论不是「A 赢」或「B 赢」,而是沉淀出一个混合工作流,它的核心思想可以概括为三句话:
- 先导航:用架构图谱建立分支地图,快速知道「这个项目有哪些链路、我该去哪找」。
- 再深潜:用代码、SQL、清单文件做真值核验,关键断言必须能回链到具体文件和行号。
- 过安检:把门禁脚本和 CI 流程当作「交接必读」,任何改动都要过清单校验和契约校验。
这套流程最初是为「多 Agent 协作交接」设计的——比如一个 Agent 负责写代码,另一个 Agent 负责审文档,两者需要共享同一套「真值」和「校验规则」。但实践下来发现,它同样适用于人与人之间的代码库交接。核心原因是一样的:人类和 AI 都面临「上下文装不下全部细节」的问题,都需要「导航 + 真值 + 安检」来降低认知负荷。
对新人最友好的交付形态
按照 Hybrid V1 产出的交接文档,包含六个固定模块:
- 冷启动清单:从装依赖到跑测试的 10-15 步可执行动作。
- 架构索引摘要:读过哪些图谱文件、与代码核验的差异注意。
- 行级锚点索引表:每个接口、每张表、每个环境变量 → 文件路径 + 函数名 + 行区间。
- 新人 FAQ:元问题(清单是什么、双轨文档怎么读)+ 行为问题(降级策略、ingest 幂等)。
- 四张改动配方卡:新增接口、调整检索策略、调整数据入库、调整 SSE 事件契约——覆盖后端改动的 80% 场景。
- 漂移防线:显式列出「文档与代码不一致」的矛盾点,把漂移当成一等公民处理。
示例:行级锚点索引表(节选,来自我仓库真实产出)
| 条目 | 文件路径 | 函数名 / 行区间 | 一句职责 |
|---|---|---|---|
GET /api/py/health | api/index.py | health @L434 | 健康检查 |
POST /api/py/chat | api/index.py | chat @L591-L980 | Legacy RAG 流式聊天 |
POST /api/py/unified/chat | api/index.py | unified_chat_route @L561 | Unified JSON 响应 |
POST /api/py/unified/chat/stream | api/index.py | unified_chat_stream_route @L576 | Unified SSE 流式 |
match_documents RPC | api/index.py | L703-L709 | 向量检索 Top-k + threshold |
keyword_documents RPC | api/index.py | fetch_keyword_hits L78-L86 | FTS 关键词检索 |
SILICONFLOW_API_KEY | api/rag_env.py | must_siliconflow_api_key L60-L64 | Embedding/Chat API Key |
示例:改动配方卡——新增 HTTP 端点(来自我仓库真实 Prompt)
每张配方卡包含三部分:必读文件、慎碰点、推荐验证命令。
【卡 A:新增 HTTP 端点】
必读文件:
- api/index.py(路由装饰器位置)
- docs/_tech_graph/_manifest.json(endpoints 数组)
- tools/tech_graph_manifest_check.py(校验逻辑)
慎碰点:
- 路径前缀 /api/py/ 是前后端约定,慎改
- handler 命名需与 manifest 一致
推荐验证:
1. 本地 curl 冒烟新端点
2. python tools/tech_graph_manifest_check.py(必须 OK)
3. python tools/tech_graph_drift_check.py(必须 OK)
4. 可选:python tools/tech_graph_render_ai.py 刷新图谱 AUTO 区块
三个可以带走的方法论
这次实验带给我的收获,远不止一个 Prompt 模板。以下是三个可以复用到任何项目的经验:
1. 统一计量语言,再谈对比
如果你的团队也在做「哪种 AI 策略更好」的实验,第一步不是跑实验,而是统一口径:
- 输入 token 怎么算?(我的经验值:代码 12 token/行,文档 10 token/行。这不是基于某个 tokenizer 的精确值,而是观察多轮实验后发现的「不会偏差太远」的近似数。Python 单行短、Java 单行长,确实有差异,但对于「量级估算」够用了。)
- 输出 token 怎么算?(中文 4 字符 ≈ 1 token,这是 Claude/GPT 类模型的大致比例)
- 时间怎么记?(外部墙钟,从 Prompt 下发到结果落盘)
没有统一口径的对比,都是玄学。
2. 让 AI 产出「可防守的文档」
交接文档的价值不在于「写得多完整」,而在于「下一次改动时会不会翻车」。我引入了三道门禁,都是脚本化、可挂进 CI 的:
清单校验:代码里的接口、RPC、表名、环境变量,是否与清单 JSON 文件一致?
这是我仓库里真实运行的脚本(tools/tech_graph_manifest_check.py 核心逻辑节选):
import re, json
from pathlib import Path
# 1. 从代码中提取真值
index_text = Path("api/index.py").read_text()
# 提取 @app.get/post("/api/py/xxx") 装饰器
endpoint_truth = re.findall(r'@app\.(get|post)\("(/api/py/[^"]+)"\)', index_text)
# 提取 .rpc("keyword_documents", {...})
api_text = "\n".join(p.read_text() for p in Path("api").glob("*.py"))
rpc_truth = set(re.findall(r'\.rpc\("([A-Za-z0-9_]+)"\s*,', api_text))
# 提取 sb.table("documents")
table_truth = set(re.findall(r'\.table\("([A-Za-z0-9_]+)"\)', api_text))
# 2. 读取清单
manifest = json.load(open("docs/_tech_graph/_manifest.json"))
manifest_endpoints = {(e["method"], e["path"]) for e in manifest["endpoints"]}
manifest_rpc = set(manifest["supabase"]["rpc"])
manifest_tables = set(manifest["supabase"]["tables"])
# 3. 对比,漂移即报错
missing_eps = [ep for ep in endpoint_truth if (ep[0].upper(), ep[1]) not in manifest_endpoints]
if missing_eps:
print(f"FAIL: 以下接口未注册清单: {missing_eps}")
exit(1)
print("OK: manifest matches code truth.")
契约校验:后端输出的 SSE 事件键名,是否在前端消费范围内?
实现方式:正则提取后端 Python 代码中的 _sse("event_name") 和 typ="..." 字符串,与契约 JSON 中的 allowed_events、type_values 做集合对比;同时正则提取前端 TypeScript 代码中的 .event === "..." 和 payload.xxx 访问,确认前端消费的键都在契约范围内。
漂移校验:代码里的关键名称,是否在架构文档中被覆盖?
实现方式:正则提取代码中的端点路径、RPC 名、表名、关键环境变量,全文搜索架构文档目录,漏掉的就报 drift。
这三道门禁写成脚本、挂进 CI,就变成了「事实护栏」——不是让人去记,而是让机器去拦。
3. 把「漂移」显式化,而不是假装没发生
每个项目都有「文档过时」的问题。我的做法是:在交接文档里专门留一个「漂移防线」章节,把发现的矛盾直接写出来。以下是我仓库真实产出中的漂移记录:
漂移 1:
docs/meta/PROJECT_CONFIG.md声称「本仓库当前未发现.github/workflows/」,但实际上存在tech-graph.yml和tech-graph-contract.yml两个 workflow。漂移 2:
99_spec.md提到.cursorrules作为规则文件,但本仓实际规则文件为.cursor/rules/*.mdc。漂移 3:Legacy chat(
api/index.py::chat)与 Unified chat(api/unified_chat.py)各自独立实现了_parse_match_threshold()、embedding 降级、keyword fallback 等逻辑,未提取到公共模块——10_flow_rag.ai.md暗示统一流程,但实现层面未完全统一。
这不是在挑刺,而是在告诉下一个接手的人:「这里有个坑,我已经帮你标出来了。」
FAQ:小项目和老项目怎么简化?
Q1:我的项目只有几百行代码,也要走这套吗?
不需要完整走。小项目的简化版:
- 跳过「架构图谱」:直接在代码文件顶部写一段模块注释(docstring),说明这个文件有哪些函数、依赖什么环境变量。
- 跳过「契约 JSON」:如果前后端没有 SSE 或复杂事件交互,用简单的 API 文档(如 OpenAPI 注释)替代。
- 保留「清单校验」的最小版本:一个 20 行的 Python 脚本,正则提取
@app.route和清单做对比即可。
核心原则:门禁的强度与项目复杂度成正比。小项目不需要重装甲,但不能完全没有护栏。
Q2:老项目没有架构图谱,怎么执行「先导航」?
让 AI 帮你生成初版图谱。具体做法:
- 给 AI 一个 Prompt:「阅读以下代码文件,产出一张模块依赖图和接口清单,用 Markdown 表格呈现。」
- AI 产出初版后,人工审核修正,落盘为
docs/tech_graph/00_main.md。 - 后续迭代中,任何改动先更新图谱,再更新代码——养成「图谱先行」的习惯。
老项目的图谱不是「一开始就完美」,而是「从 0 到 1 再持续维护」。
写在最后
AI 辅助开发的最大瓶颈,往往不是模型不够聪明,而是上下文窗口装不下一个项目的全部细节。我的实验不是为了证明「某种策略最优」,而是为了找到一种在有限上下文下,最大化信息密度和可审计性的工作流。
Hybrid V1 不是终点。随着项目演进,架构图谱会更新,门禁脚本会增强,配方卡会扩充。但核心原则不会变:
导航帮你找方向,真值帮你做判断,安检帮你防翻车。
如果你也在探索 AI 辅助代码库交接的最佳实践,希望这篇记录能给你一些启发。