引言:为什么要写这篇复盘
这篇文章不是学习起点。在动笔之前,我已经在四个方向上做过实际工程:AI 代码审查工具(gitea-ai-assistant)、RAG 知识库(《大模型 RAG 技术全景解读》)、Agent 上下文治理(《Agent 上下文治理:从"定时清空垃圾桶"到"持续耕耘知识矿脉"》)以及告警分析智能体。每件事单独做的时候,我学到的是零散的点;把它们放在一起看,才看清一条清晰的认知主线:
模型调用 → 知识检索 → Agent 编排 → 工程治理
这是一条从"能用"到"能用准",从"能自主"到"能生产"的递进路线。本文是对这条线的复盘——每个实践解决了什么问题、我当时错在哪、现在回头看学到了什么。
一、模型调用:AI 代码审查工具的演进
项目概况
gitea-ai-assistant 是 Gitea 平台的 AI 审查助手:通过 Webhook 接收 PR 和提交事件,自动执行代码审查,把总结评论和行级评论回写到 Gitea。项目最初只有一条简单链路(Webhook → OpenAI → 评论),如今已经长成多引擎架构:
- 动态 Agent 引擎:主审查 agent 自主派生子 agent 做聚焦分析
- Codex 引擎:Codex CLI 作为另一条审查管线
- 可插拔 LLM provider:OpenAI Compatible、OpenAI Responses、Anthropic、Gemini
- Web Admin UI:运行时配置 provider、模型、Webhook、审查策略
- 通知:飞书 + 企业微信
- 安全:Webhook 签名验证 + AES-256-GCM 加密存储 API Key
认知一:模型调用层的第一课是"抽象",不是"接入"
项目早期把 OpenAI 调用直接写死在业务代码里,换模型服务等于重写一遍。后来抽出独立的 llm/ 层,统一走 OpenAI Compatible 协议,Anthropic、Gemini 变成可插拔实现。这个改动让一个审查工具从"绑定某一家 API"变成"绑定一个协议"。
教训:AI 应用的模型调用层,本质是协议适配层。厂商 API 差异极大(流式格式、工具调用协议、缓存语义各不相同),尽早抽象、尽早隔离,否则业务代码会被模型 API 的演进拖死。
认知二:异步处理是 AI 服务的"物理定律"
LLM 审查一个 PR 耗时数十秒,Webhook 请求不可能同步等它。项目的做法是标准的:
// 立即响应 Webhook,不阻塞 Gitea
return c.json({ status: 'accepted', message: '审查请求已接受' }, 202);
// 后台异步执行耗时审查
reviewPullRequest(owner, repoName, prNumber).catch(error => {
logger.error('审查 PR 失败:', error);
});
慢任务必须异步化,这几乎是 AI 应用的强制约束。代价是状态管理变复杂(任务何时完成、失败如何重试、结果如何回写),这为后面的"Agent 编排"认知埋下了伏笔。
认知三:安全不是配置项,是设计约束
Webhook 是公网暴露的入口,签名验证是第一道防线。项目实现(核对自真实源码):
function verifyWebhookSignature(body: string, signature: string): boolean {
if (!config.app.webhookSecret) {
logger.warn('未配置 Webhook 密钥,跳过签名验证');
return false;
}
const hmac = crypto.createHmac('sha256', config.app.webhookSecret);
hmac.update(body);
const calculatedSignature = hmac.digest('hex');
if (!signature) {
logger.warn('请求中无签名头');
return false;
}
// Gitea 的 X-Gitea-Signature 是无前缀的纯 hex(区别于 GitHub 的 sha256= 前缀)
try {
return crypto.timingSafeEqual(
Buffer.from(calculatedSignature),
Buffer.from(signature)
);
} catch (error) {
logger.error('签名验证失败', error);
return false;
}
}
三个细节都来自踩坑:空签名要单独判断(否则时序比较会先崩);timingSafeEqual 在两段长度不等时会抛异常而不是返回 false,必须 try/catch;Gitea 与 GitHub 的签名格式不同(一个无前缀、一个 sha256= 前缀),协议细节不能想当然。
更进一步的认知是 API Key 不能明文落库——项目用 AES-256-GCM 加密存储,密钥从环境变量注入。模型调用层的安全 = 入站校验(Webhook 签名)+ 出站保密(密钥加密)+ 最小暴露(环境变量)。
二、知识检索:RAG 的"检索质量决定论"
如果说代码审查教会我"怎么调用模型",RAG 教会我的则是"调用之前,先解决检索"。
我的 RAG 实践(详见《大模型 RAG 技术全景解读:从原理到高阶实战》)经历了三个阶段:
第一阶段:以为 RAG = 向量数据库 + LLM。 把文档切开、embedding、存进向量库、拼 prompt,能答了,但答不准。术语匹配失效(产品型号、错误码、人名),跨段落的问题检索不到。
第二阶段:认识到检索质量由上游决定。 分块策略、混合检索(向量 + BM25)、重排序(Cross-encoder)才是差距所在。Databricks 的统计是基础 RAG 检索失败率高达 40%;Anthropic 的 Contextual Retrieval 论文里,只加 Contextual Retrieval 就让 top-20 检索失败率降 49%,叠加 Reranker 后降 67%——这些优化全发生在"检索"环节,跟生成模型无关。
第三阶段:评估先行。 没有评估的 RAG 就是没装后视镜开车。用 Ragas 建立 faithfulness / answer relevancy / context precision / context recall 四个指标的评估集,并把评估接进 CI——改分块、改 prompt、改检索参数后自动跑分,低于阈值拦发布。
认知主线:检索质量是 RAG 的上限,生成模型只是逼近这个上限。而检索质量不是模型给的,是分块、索引、重排、评估这套工程体系给的。
三、Agent 编排:从"写工具"到"管理上下文"
gitea-ai-assistant 的下一代架构引入了真正的 Agent 引擎:主审查 agent 不再一次性地把整个 diff 丢给模型,而是自主派生子 agent,每个子 agent 聚焦一个文件或一个关注点,再把结论汇总。这是从"单次调用"到"编排"的跃迁。
但编排一旦深入,我立刻撞上第二个问题:Agent 的能力上限由上下文管理决定,而不是由模型决定。
上下文一长,就出现教科书式的困境:压缩丢信息、缓存报废、跨会话失忆。这也是为什么我会在《Agent 上下文治理》里用整套篇幅论证一件事——上下文不是需要清空的垃圾桶,而是需要持续耕耘的矿脉。Magic Context 的方案是三层时间架构:
| 层次 | 节奏 | 职责 |
|---|---|---|
| 实时层 | 每轮 transform,零 LLM 调用 | 标签打标、丢弃排队、启发式清理 |
| 异步层 | historian 触发,秒到分钟级 | 分段历史、分层 compartment、事实提取 |
| 离线层 | dreamer 调度,小时到天级 | 记忆合并、验证、归档、文档维护 |
两个认知对我冲击最大:
丢弃操作必须延迟到缓存自然过期再执行。在缓存命中时改 prompt 前缀,等于为每个小操作付一次全量缓存报废的账单。把操作排队到缓存过期边界,一次报废做完所有累积的清理——"操作对齐到自然边界"这个原则,从缓存一直延伸到我后来做的所有后台系统。
确定性优先于智能。LLM 压缩不可重现,同样的对话每次压缩字节都不同,缓存永远不稳定。而纯数学的 decay 渲染是纯函数——相同的输入永远产出相同的字节。在系统关键路径上,能用确定性算法就绝不用 LLM。
四、工程治理:把 AI 应用当成系统来运维
四个实践中,告警分析智能体是离"工程治理"最近的一个:把监控告警流交给 agent 做聚合、根因分析和上下文关联,把"半夜一条告警、人工翻半天日志"变成"告警 + 关联上下文 + 初步根因"。
从这些实践里沉淀出的工程治理原则,其实和普通后端系统一致,只是 AI 场景放大了每一项:
- 可观测性:LLM 调用是黑盒,必须记录每次调用的输入输出、token 消耗、延迟。没有 trace 的 AI 应用等于盲飞。
- 成本治理:token 是持续账单。缓存(prompt cache、embedding cache)、精简 prompt、路由判断(不必每次查询都检索)是三大抓手。
- 密钥与安全:API Key 加密存储、最小权限、Webhook 入站校验——AI 应用的攻击面比普通 Web 服务多了一层"提示词注入"。
- 优雅降级:模型服务会挂、会限流、会变更协议。异步任务队列 + 重试 + 降级策略不是可选项。
五、认知主线:四个阶段的递进
回头看,四个实践恰好铺成一条阶梯:
| 阶段 | 核心问题 | 关键认知 | 代表实践 |
|---|---|---|---|
| 模型调用 | 怎么调模型 | 协议抽象、异步化、密钥安全 | gitea-ai-assistant |
| 知识检索 | 怎么喂对上下文 | 检索质量决定上限、评估先行 | RAG 知识库 |
| Agent 编排 | 怎么组织多步任务 | 子 agent 分解、上下文治理 | Agent 审查引擎、Magic Context |
| 工程治理 | 怎么让它稳定跑 | 可观测、成本、安全、降级 | 告警分析智能体 |
每一层的失败都会成为下一层的地基:调不好模型,检索再准也白搭;检索不准,Agent 的每一步决策都在垃圾上下文上做;编排再精巧,上下文管不好就是空中楼阁;而前三层都成立之后,没有工程治理,系统活不过生产环境的第一周。
结语
从零散实践到系统化认知,最大的变化不是会用的工具变多了,而是看问题的层次变了:
- 早期我会问"用哪个模型"——现在我问"模型调用层怎么抽象,才不被厂商绑架"
- 中期我会问"向量检索准不准"——现在我问"检索质量由哪个上游环节决定,怎么评估"
- 后来我会问"agent 能不能自己完成任务"——现在我问"它的上下文生命周期有没有治理"
- 现在我会问"这个系统在没人盯着的时候,能不能自己活下来"
AI 应用工程没有银弹。但有一条确定的主线:模型调用是入口,知识检索是质量,Agent 编排是上限,工程治理是底线。这四层没有哪一层可以跳过,也没有哪一层可以一劳永逸——它们是随着实践深度持续重写的过程,也是这篇复盘想留下的东西。