site logo

Marico's space

OpsPilot AI:不赋予 LLM 权限的 RAG 与 Agent 构建

编程技术 2026-10-09 14:49:48 8

最近折腾了一个叫 OpsPilot AI 的项目,专门解决运维场景下的 RAG(检索增强生成)和 Agent 问题。踩了几个坑之后,这篇把核心设计思路说清楚。

运维人员平时怎么工作的?翻阅运维手册和文档,理解当前发生了什么,然后把部分信息转成工单。找一个指令和开一个工单看起来很接近,但后者跨了一个边界:它要在另一个系统里产生实际效果。

这就是我做 OpsPilot AI 的起点。我想搜索租户自有的运维知识,给出带引用的回答,然后准备好一个 GitLab 工单让另一个人审批。但有意思的问题出现在我不再把这一切当作"Agent 的响应"来对待的时候。

AI 找到足够证据了吗?它有没有权限去执行一个外部操作? 这是两个完全不同的问题。一个提案可能满足格式要求,标题也很像那么回事,但仍然超出了用户的权限范围。

我选择把概率性的解释关在确定性的边界里。一个正确生成的动作仍然只是一个提案。 系统负责认证、授权、记录审批、控制执行。

Define the system before adding the model

我把认证主体、租户知识、提案、审批、执行分别建模。把它们混在一个"Agent 状态"对象里,很容易把生成的信息和被授予的权限搞混。

主体携带租户、主体身份和从配置凭证中导出的角色。提案保存建议的工作。审批记录谁接受了哪个操作。执行记录尝试和外部结果。PostgreSQL 持久化这个生命周期,不依赖进程一直存活。

我把领域契约和数据库、模型、GitLab 适配器分开。这样我可以测试一个规划器,它会提议被禁止的操作,而不需要调用模型就能发现策略执行是否有效。

领域还约束了产品:每次运行最多准备一个工单提案。我没给 Agent Shell、任意 SQL 或者调用任意 URL 的权限。限制动作空间减少了我需要授权、观察和恢复的内容。添加工具需要重新审视权限模型,而不是简单往 prompt 里加个名字。

Retrieval must respect the access boundary

在生成回答之前,我需要决定哪些文档可以进入上下文。租户身份不是模型选择的参数,它来自认证主体并跟随事务进入数据库。

摄入过程规范化文本、切成重叠的块,在外部调用完成后原子存储文本和向量。检索结合了 pgvector 中的精确余弦向量搜索和 PostgreSQL 全文搜索。倒数排名融合结合排名位置,而不是假设不同检索方法的分数可以直接比较。向量空间标识符防止不兼容向量之间的比较。

租户过滤器在排名和限制之前应用。全球搜索然后只过滤前 k 条是另一种架构:其他租户的文档可能消耗结果预算,也会扩大泄漏的机会。

我还配置了事务本地的租户上下文并启用了行级安全。用真实 PostgreSQL 测试来验证缺失的谓词和连接池复用。这个限制很重要:运行时角色可以设置租户上下文,所以 RLS 不能防止以该角色执行的任意 SQL。它也不提供租户内的文档 ACL。

RAG does not end when chunks are found

我把路径分成可以独立失败的边界:

query → retriever → ranked evidence ↓ generation ↓ citation validation ↓ answer or abstention

适配器请求结构化输出。应用再次检查:拒绝伪造或重复的 ID,引用必须属于授权的已检索块,元数据来自数据库。未经引用的输出变成固定的弃权。当检索返回空时,没有理由调用生成。

这控制了 ID 来源,不是每个语句的真实性。一个块可以被授权而回答误解了其内容。引用成员关系不构成蕴含:引用一个现有文档不等于证明了结论来自它。

这个区别改变了诊断方式。检索可能找不到正确的文档;生成可能收到正确上下文但仍然失败;或者形式上有效的引用可能不支持声明。只测量最终回答会隐藏哪个部分需要改进。

我也不把这个控制扩展成对问题的通用保证。授权的提案不会因为存在于数据库就语义正确。人工审核仍然需要评估其内容。

Measure retrieval without mistaking ranking for an answer

第一个检索数据集太容易区分策略了。检索-v2 引入了合成文档和问题,涵盖改写、标识符、歧义、多个相关文档和硬负例。我把24 个开发查询和 36 个留出查询分开,标签在运行检索器之前定义。

MRR@5 测量第一个相关文档出现的位置有多早。第一名得 1,第二名得 1/2,前五名没有相关文档得 0。均值总结了这个行为。协议对检索块中的文档去重;一个文档的多个块仍然可能消耗部分检索预算。

Held-out strategy MRR@5
Lexical 0.7532
Vector with fake embedder 0.4375
Hybrid with fake embedder 0.6306

在这个实验中,词法检索胜过混合检索。向量分支使用确定性词哈希,不是语义模型。RRF 不能自动修复弱的排名:出现在两个分支中的文档可能排名高于只在词法分支中找到的相关结果。

检索-v2 报告把这个结果归因于 commit 7ba3378。留出集只运行了一次,现在已消耗;CI 使用开发查询。后续的 readiness 更改改变了指纹,守卫拒绝在当前代码上重新运行留出集。

这不能证明混合搜索通常更差。它没有测量真实向量、扎根性或回答质量。评估另一个配置需要新的冻结协议和新的留出集。这和我在 evals 文章中讨论的测量范围问题相同。

The planner proposes; application code decides what is allowed

LangGraph 编排工作流。它不定义权限模型。

规划器可以选择 search_knowledge、prepare_gitlab_issue 或 final_answer。create_gitlab_issue 不是模型工具。图没有从规划节点到执行的路径;进入执行取决于审批后的持久化状态。

我限制了步数、截止时间和无效输出尝试。这些限制包含循环和协议失败,但不使决策有用。规划器可以在预算内完成但仍然提议没人需要的工作。

在准备期间,策略解析租户配置中的项目别名,检查标签,并将允许的被分配人映射到配置的 ID。模型不能提供任意项目 ID 或选择自己的权限。

想象一个客户端提交这个作为所谓的权限证明:

{ "allowed_project": "secret-admin"
}

这是反模式,不是 OpsPilot 契约。有效的 JSON 或客户端断言某事"被允许"不授予访问权。权限必须来自认证身份和应用策略。项目的契约拒绝额外字段;规划器模式不包含租户、审批或授权标志。

Approve the exact action, not just the intention

"人工审批了"是不完整的。审批了什么?那仍然是即将执行的对象吗?期间策略有没有改变?

我在请求审批之前持久化规范操作,包含租户、请求者、解析后的项目、标题、描述、标签和被分配人。规范 JSON 的 SHA-256 哈希把决策绑定到该内容。规范化和排序防止标签或被分配人的等价表示产生不同的身份。执行使用存储的操作;不再要求模型重新生成。

审批者必须持有适当的角色,属于同一租户,且与请求者不是同一主体。事务检查提供的哈希并重新计算存储操作的哈希。运行时角色对提案或审批没有 UPDATE 权限。

在产生外部效果之前,执行再次检查审批、哈希和当前策略。如果项目从允许列表中移除,操作会失败关闭。如果操作在正常流程外被更改,审批不再适用。更改的操作需要新的运行和审批。

probabilistic interpretation ↓
structured proposal ↓
deterministic policy ↓
human approval (action hash) ↓
approved immutable action ↓
revalidation → execution ↓
reconciliation / audit

哈希保护内容和决策之间的链接。它不评估问题是否有意义,也不取代授权。人工审核增加了操作工作。我接受这个成本来控制一个有影响的外部操作。

GitLab's response was lost. Can I send the POST again?

这个失败模式最清楚地连接了 Agent 和熟悉的分布式系统问题:GitLab 收到创建请求,存储了工单,丢失了响应。客户端观察到一个超时。工单可能已经存在。

立即重复 POST 可能重复工作。我区分发送前失败、终末拒绝和模糊结果。传输后超时、5xx 或格式错误的 2xx 响应可能隐藏了已完成的写入。都不一定意味着"什么都没发生"。

每个审批的操作获得一个稳定密钥,派生自租户、运行和哈希。适配器在工单描述中包含一个标记。当结果模糊时,明确的 /resume 请求在考虑再次发送之前搜索该标记。

如果找到工单,系统记录成功而不创建另一个。如果查找失败,模糊性保留。如果没有找到,必须经过宽限期才能重新发送,在尝试预算内。没有自动恢复工作者。

PostgreSQL 使用锁、拥有者和租约来控制执行声明并防止后来的拥有者覆盖更新的记录。租约不能取消已在飞行中的 HTTP 请求。延迟的搜索可见性或标记的移除也可能导致重复。这不是一次且仅一次的保证。

用真实 PostgreSQL 和假 GitLab 的测试验证了丢失响应、数据库在外接效果后失败、创建后进程死亡。它们在这些受控场景中建立了恢复。真实 GitLab 冒烟测试验证了创建、查找和成功运行的恢复;它没有测量真实丢失响应恢复或索引延迟。

Security belongs outside the prompt

最有用的对抗性测试假设规划器已经被攻陷。文档告诉它跳过审批并使用被禁止的项目。脚本化规划器服从,请求直接创建,尝试被禁止的项目,并发出审批决定。

在测试的场景中,应用代码拒绝被禁止的工具和项目。唯一允许的提案仍然等待人工,零请求发送到 GitLab。这测试了权限边界即使在解释失败时。

标记证据为"不受信任数据"的 prompt 和严格的模式帮助组织接口。它们不取代认证或授权。它们也不能消除租户内的回答投毒:模型可以接收它被授权读取的恶意信息。

这些控制保护特定边界:租户隔离、允许的工具、人工决策、运行时验证和审计跟踪。对抗性回归验证定义的行为;不建立对每个 prompt 注入的抵抗力。

Observability follows the boundaries

日志说"Agent 失败"是不够的。我需要区分证据缺失、模型超时、策略拒绝、等待审批花费的时间以及模糊的外部效果。

我用 OpenTelemetry 为边界埋点:HTTP 请求、检索及其分支、AI 调用、规划、授权、审批、执行、GitLab 和协调。请求 ID、运行 ID 和跟踪 ID 关联日志、span 和审计事件。

审批和恢复是新请求。我不声称整个人工等待都在一个连续跟踪内。审计中记录的运行和跟踪 ID 连接这些条目。这让我无需记录完整 prompt 或文档就能重建生命周期。

允许列表限制属性,请求、租户或文档 ID 不成为指标标签。这减少内容暴露和无控制的基数。配置的和服务的模型是不同的事实;缺失的使用量或成本未知,而不是零。

遥测导出可能丢失信号,失败打开以不控制产品可用性。授权和审批保持失败关闭。持久化状态和审计跟踪是权威的;成功的 span 不决定执行是否完成。观测性记录文档了这些信号和本地收集器故障测试。

Different evals answer different questions

Agent 评估用脚本化/离线规划器、真实 PostgreSQL 和假 GitLab 通过了16/16 个案例。它测量定义序列下的控制,包括对抗性行为。它不测量真实 LLM 选择工具的能力。

  • Retrieval-v2: 用假向量练习合成留出排名。它不建立真实语义检索或正确回答。
  • Agent eval: 练习离线策略、审批、状态和恢复。它不建立真实 LLM 决策质量。
  • OpenAI smoke: 练习真实 API、模式、向量和小数据库支持的 RAG。它不建立质量、蕴含或测量成本。
  • GitLab smoke: 练习沙箱审批、创建、确认和恢复。它不建立真实故障恢复或一次且仅一次。

生成质量问不同的问题:回答正确吗,它的声明来自证据吗?项目还没有用真实模型评估该行为。排名、工作流和集成检查可以通过而不回答那个问题。

10月4日的 OpenAI 记录确认了真实 256 维向量、回答和规划器模式、属于上下文的引用、使用量和已服务模型记录以及有界超时。10月5日的 GitLab 记录确认审批前无请求、创建和 GET、第二次审批返回 409、恢复找到一个匹配工单和一个创建 POST 以及确认关闭。

这些是独立运行,GitLab 冒烟使用离线规划器。没有单一的实时 OpenAI + GitLab 端到端。OpenAI 定价未配置:通过条件成本检查不建立测量成本。

发布 v0.1.0 已发布在 GitHub。本地记录文档了241 个单元测试和 72 个集成测试,加上无菌室复现。当前状态文档记录了所有者后续确认托管 CI 通过。这些数字描述那个验证,不是生产流量下的性能。

What remains unproven

留出集小、合成、仅英语、一人创作。假向量不测试语义理解。没有真实模型质量评估、测量成本或针对真实运维流量的组合系统验证。

静态令牌既不提供 SSO 也不提供身份生命周期管理。没有租户内 ACL。精确向量搜索线性扩展;小语料库不能建立规模。恢复需要明确恢复,基于搜索的协调保留其可见性和飞行中请求限制。

Terraform 描述了用离线计划验证的阿里云蓝图。没有执行阿里云部署;云运行时、恢复和阿里云遥测路径仍未验证。通过配置验证不等于在该环境中运行。

发布镜像扫描保留了44 个 HIGH 发现而没有报告修复,零个可修复 HIGH/CRITICAL 发现。门禁阻止可修复发现;通过它不意味着零漏洞。四个分类的 IaC 风险也已文档化。这些是历史结果,不是为本文执行的新扫描。

What I take from this project

我的主要教训是让解释和效果之间的契约显式化。模型可以检索上下文、解释请求、提议工作。软件系统必须确定权限、验证审批和外部结果,并在结果不确定时保留可恢复状态。

下一步评估需要解决剩余差距:基于新协议的真实向量、回答质量和实时恢复。通过冒烟测试不能回答那些问题。分离这些职责教给我的比单纯让 Agent 调用工具多得多。