确定性网关:AI Agent接入企业系统的安全控制层 📅 发布时间:2026/8/30 16:17:35 👁 浏览次数: 在 Hacker News 上看到 Stonefold 这个项目时我第一反应不是“又一个 AI 网关”而是它把 AI Agent 接入系统时最容易被忽略的问题说清楚了模型可以自由发挥但系统必须稳定可靠。现在很多团队做大模型应用最痛苦的点已经不是 Prompt 写不好、模型不够聪明而是 Agent 一旦开始调用真实业务 API你就要面对完全不可控的后果。模型可能产生幻觉、可能把参数名映射错、可能反复重试同一个写操作、可能在一个并未授权的上下文里访问内网服务。你很难靠“提示词约束”解决这些问题因为提示词本质上是概率性的。Stonefold 给出的答案是在 Agent 和你的系统之间加一层确定性的网关。所谓确定性就是请求能不能通过、会去哪个后端、返回什么格式都由规则和可验证的代码决定而不是由模型在现场临时判断。这个项目会在 Show HN 引起讨论不是因为它是新的 Agent 框架而是它把“控制的职责”从模型手里拿回了工程师手里。读完这篇文章你会明白三件事AI Agent 为什么不能直接调内网系统一个最小可用的确定性网关应该怎么落地如何用网关日志把 AI Agent 的评测Evals从“凭感觉打分”变成可断言的工程测试。1. AI Agent 接入系统痛点到底在哪里先看一个非常常见的场景。团队基于大模型做了一个“智能客服助手”它需要查询订单、创建售后单、更新用户地址。一开始你直接给 Agent 暴露了三个后端 API 的 SDK。一切看起来很简单但上线后问题不断。1.1 模型输出天然不确定大模型生成工具调用参数时本质上是按概率采样。同一个用户问题模型可能这次把customer_id解析成C001下次却解析成001。如果后端 API 对参数有严格要求这种细微不稳定就会变成线上错误。更麻烦的是模型偶尔会把amount和quantity搞混甚至在没有得到用户确认时直接发起一次“删除”操作。你可以在 Prompt 里写“未经确认不得删除”但大模型对这类指令的理解并不是 100% 稳定。1.2 权限边界难以收拢直接让 Agent 调用后端 API意味着 Agent 拥有的权限约等于后端 API 的权限。如果后端服务的 API Key 权限过宽Agent 一旦被诱导或误用可能访问到客户管理、订单导出、配置修改等高危接口。传统服务间调用有明确的账号体系、IP 白名单、OAuth Scope而 Agent 调用时你很难判断“这次调用背后是哪个对话、哪个用户、哪条业务规则”。在不确定来源的请求进来之前必须有一个确定的关卡把身份、权限、操作边界全部校完。1.3 审计和排障困难Agent 与后端之间如果只是点对点调用出了问题你会看到两边的日志但很难还原完整链路。Agent 决定调用什么工具是模型推理的结果后端是否接受则取决于后端逻辑。中间缺一层记录“模型意图、实际请求、最终决策”的审计层。换句话说直接对接的架构里“模型意图”和“系统事实”是脱节的。出了事故你很难回答到底是模型选错了工具还是后端参数校验没做好1.4 评测Evals没有稳定锚点现在 AI Agent 评测越来越受重视但很多团队的 eval 还停留在“回答是否正确”。对于会调用系统的 Agent 来说真正重要的不是“答了什么”而是“做了什么”。如果中间没有一层确定性网关你很难断言“这次 Agent 调用是否越权”“这个危险操作是否被拦截”。Stonefold 这类确定性网关正好把这些痛点收敛到了一层。它不负责让模型更聪明它负责让 Agent 的行为更可预期。2. 确定性网关是什么Stonefold 带来的架构变化Stonefold 的定位从项目名称就可以看出在 AI Agent 和你的系统之间充当一个确定性的网关。这里的“网关”不是传统意义上的负载均衡或反向代理而是一个专门面向 Agent 工具调用的控制层。传统架构中Agent 直接调用业务系统Agent - 企业内部系统引入确定性网关后调用链路变成Agent - Stonefold 确定性网关 - 企业内部系统这个中间层会做几件非常关键的事身份认证确认当前 Agent 是谁属于哪个应用拥有哪些权限范围。需求映射把 Agent 的“自然语言意图”映射成“系统允许执行的操作”。白名单校验目标服务、路径、方法必须在配置里存在。参数校验请求体是否符合 JSON Schema字段名、类型、取值范围是否合法。操作审计记录每一次调用的完整决策过程供后续追查和评估。真正容易忽略的是“确定性”这三个字。在 Agent 链路里模型选择调用哪个工具本身是概率性的。但网关的决策不应该是概率性的。网关不应该问“这个请求看起来合理要不要放行”而应该问“这个请求是否匹配某条明确规则”。规则存在就是放行规则不存在就是拦截。这个二元决策模型才是 Stonefold 这类项目最核心的价值。用一个类比来理解大模型像一位很有创造力的员工他能在不确定的信息里做判断但企业系统不应该是这位员工想改什么就改什么。你必须给他一张“门禁卡”规定他只能进哪些门、只能动哪些东西。Stonefold 就是发门禁卡的保安而且是完全按手册办事、不讲情面的保安。所以Stonefold 并不是要和 LangChain、AutoGPT 这样的 Agent 框架抢位置它是 Agent 框架与真实系统之间的基础设施层。无论 Agent 用的是 OpenAI Function Calling、Claude Tools还是自己实现的工具调度逻辑最终的工具调用命令都应该先经过这一层。3. 确定性网关和传统 API 网关有什么区别很多人一听到“网关”就会想到 Kong、Nginx、Spring Cloud Gateway。但 Stonefold 这类确定性网关和传统 API 网关解决的问题并不完全一样。两者有重叠但不能互相替代。维度传统 API 网关Stonefold 这类确定性网关主要调用方App、前端、服务间调用AI Agent、Copilot、自动化决策程序请求生成方式代码固定调用参数相对稳定大模型动态生成可能产生幻觉、参数漂移核心校验认证、限流、路由、负载均衡意图到操作的合法映射、Schema 校验、权限边界约束决策方式确定性规则确定性规则但更强调“与 Agent 能力解耦”返回结果直接返回后端响应把错误和拒绝结果转成 Agent 可理解的结构化反馈日志价值访问日志、监控指标工具调用轨迹、Agent 评测依据、合规审计从这个表格可以看出确定性网关是“更有立场”的一层。传统 API 网关通常假设调用方是可信的只要认证通过、限流未超就把请求转发到后端。但 AI Agent 的调用不能默认可信因为同一个 Agent 可能面对完全不同的用户、对话和多轮上下文。传统 API 网关也能做鉴权但它的粒度往往是“应用级”的。Stonefold 强调的则是“操作级”甚至“参数级”的约束不仅判断你能不能调用订单服务还要判断你调用的参数是否符合业务规则。举个例子一个 Agent 在对话里说“帮我把订单 12345 的金额改成 100 元”。如果只靠传统 API 网关只要 Agent 有订单服务的写权限这个请求就会被转发。但 Stonefold 这类网关可以额外校验当前 Agent 是否被允许修改订单金额修改金额的参数是否来自业务允许的枚举有没有对应的审批单号这些规则不需要大模型理解它们写在配置里由工程师审核并发布。Stonefold 的确定性本质上就是把“业务的不可谈判规则”从模型能力中剥离出来。4. 核心组件与工作流程拆解虽然 Stonefold 的源码和安装方式需要参考官方项目但从“确定性网关”这一类系统的通用设计来看核心组件可以拆成这样五块。理解了这五块你就能自己实现一个最小版本也能读明白 Stonefold 设计的思路。4.1 请求接入层接入层接收 Agent 发来的工具调用请求。它通常是 HTTP 接口也可以是 SDK 或消息队列入口。请求里必须带 Agent 身份信息和操作意图。在 Stonefold 场景里请求体不应该只是一个“自然语言问句”而应该是结构化的工具调用例如{ intent: 创建发票, service: invoice_service, path: /v1/invoices, payload: { customer_id: C001, amount: 100, currency: CNY } }这个格式看起来很简单但它给下游校验提供了明确输入。如果没有这个结构化入口网关就需要去解析自然语言那又回到了不确定的怪圈。4.2 身份与权限解析接入层下一步是认证。每个 Agent 有自己独立的身份标识网关根据身份解析出它可以访问的服务和操作范围。这里要强调的是最小权限原则一个用于查询订单的 Agent不应该拥有删除订单的权限一个用于测试的 Agent不应该拥有生产环境的权限。身份解析和权限解析应该全部是确定性的。它不依赖模型判断也不依赖请求文本。只要 Token 没有匹配到身份请求立即失败。4.3 路由白名单校验网关里要维护一份“该 Agent 能访问的服务、路径、方法”的清单。这份清单通常放到配置中心或代码仓库里经过评审后发布。校验逻辑很简单目标服务是否存在于配置中服务下的路径是否允许HTTP 方法是否允许如果有一个不符合直接拒绝。这里最忌“宽松匹配”。比如配置了/v1/invoices那/v1/invoices/delete_all这些额外路径就应该被当作不存在而不是按前缀放行。4.4 参数 Schema 校验Agent 生成的参数经常不靠谱。比如数字类型写成字符串、必填字段缺失、枚举值不在业务允许范围、多传了不该传的字段。网关应基于 JSON Schema 或类似的校验机制对请求体做强校验。additionalProperties: false这个配置在 Agent 场景里非常重要。模型经常会在用户要求外附加一些字段如果网关不拦截这些字段可能被后端意外消费。严格模式会让 Agent 学会只发送系统声明的字段。4.5 审计与观测每一次被允许或被拒绝的请求都应该记录成一条审计事件。这条事件至少要包含请求时间Agent 身份目标服务与路径请求内容决策结果allow / deny拒绝原因后端返回结果这些数据不只是给人看的在后面做 Agent 评测时它就是“事实标准”。Stonefold 这类网关把安全控制和可观测性绑定在一起这是它和普通 API 网关的重要差异。一个完整的请求流程可以概括为Agent 生成结构化工具调用请求。请求携带身份 Token 发给网关。网关完成身份认证拒绝未知 Token。网关检查目标服务、路径、方法白名单。网关校验请求体是否符合 Schema。网关记录一条请求审计事件。网关转发到内部系统并校验返回结果。网关把后端响应返回给 Agent。整个过程里没有任何一步需要大模型参与理解。这就是“确定性网关”的含义。5. 从零实现一个最小确定性网关虽然 Stonefold 本身值得关注但不用等官方中文文档出来你也能用半小时写一个最小可用的确定性网关把核心思路跑通。下面这个示例不依赖特定发行版重点演示通用实现路径。5.1 环境准备本文示例使用 Python 3.10需要安装以下依赖pip install fastapi uvicorn pyyaml jsonschema如果使用更低版本注意str.removeprefix需要 Python 3.9。工程目录结构建议如下gateway_demo/ ├── gateway.py ├── gateway_config.yaml └── schemas/ └── invoice_create.json5.2 配置文件首先定义一份最小配置。配置里包含 Agent 身份和允许访问的服务路径。# gateway_demo/gateway_config.yaml agents: - name: billing-agent token: demo-secret-token-123 services: - name: invoice_service paths: - path: /v1/invoices methods: [POST, GET] schema_file: schemas/invoice_create.json这个配置的含义是只有billing-agent能使用 Tokendemo-secret-token-123而且只能访问invoice_service下的/v1/invoices路径。默认情况下其他服务、其他路径都不可访问。5.3 参数 Schema为创建发票定义严格的 JSON Schema。这里故意设置additionalProperties: false用来拦截模型产生的多余字段。{ $schema: https://json-schema.org/draft/2020-12/schema, type: object, required: [customer_id, amount], properties: { customer_id: { type: string, minLength: 1 }, amount: { type: number, minimum: 0.01 }, currency: { type: string, enum: [CNY, USD, EUR] } }, additionalProperties: false }这里amount必须是数字类型。如果 Agent 把100传成字符串100网关要立即拒绝避免下游系统做隐式转换后产生错误。5.4 网关主体代码下面实现一个 Stonefold 风格的最小确定性网关# gateway_demo/gateway.py # 这是一个演示确定性网关思路的最小实现 import json from typing import Optional import jsonschema import yaml from fastapi import FastAPI, Header, HTTPException from pydantic import BaseModel app FastAPI(titleDeterministic Agent Gateway Demo) def load_config(path: str gateway_config.yaml): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) CONFIG load_config() service_map {} for svc in CONFIG[services]: service_map[svc[name]] svc class AgentAction(BaseModel): intent: str service: str path: str payload: dict class Gateway: 所有 Agent 请求都先经过这个类做确定性校验。 def __init__(self, config): self.config config self.audit_log: list [] def authorize(self, token: str) - Optional[str]: for agent in self.config.get(agents, []): if agent[token] token: return agent[name] return None def check_service_and_path(self, service_name: str, path: str) - bool: service service_map.get(service_name) if not service: return False for rule in service[paths]: if rule[path] path: return True return False def validate_payload(self, service_name: str, path: str, payload: dict) - Optional[str]: service service_map.get(service_name) if not service: return unknown_service for rule in service[paths]: if rule[path] ! path: continue schema_file rule.get(schema_file) if not schema_file: return None with open(schema_file, r, encodingutf-8) as f: schema json.load(f) try: jsonschema.validate(instancepayload, schemaschema) except jsonschema.ValidationError as e: return e.message return None return path_not_found def execute(self, action: AgentAction, agent_name: str) - dict: # 真实项目中这里应调用内部系统并记录返回结果 return { agent: agent_name, service: action.service, path: action.path, payload: action.payload, backend_result: ok, } gateway Gateway(CONFIG) app.post(/agent/execute) async def agent_execute(action: AgentAction, authorization: str Header(...)): token authorization.removeprefix(Bearer ).strip() agent_name gateway.authorize(token) if not agent_name: raise HTTPException(status_code401, detailinvalid token) if not gateway.check_service_and_path(action.service, action.path): raise HTTPException(status_code403, detailservice or path not allowed) error gateway.validate_payload(action.service, action.path, action.payload) if error: raise HTTPException(status_code422, detailfpayload invalid: {error}) result gateway.execute(action, agent_name) gateway.audit_log.append({ agent: agent_name, service: action.service, path: action.path, action: action.dict(), decision: allow, }) return {decision: allow, result: result}这段代码的逻辑顺序非常重要先认证再白名单再 Schema 校验最后才执行。任何一个环节失败请求都不能到达后端。这里我用audit_log内存列表表示审计日志。真实项目里这一步应该持久化到 ClickHouse、Elasticsearch、PostgreSQL 或 S3 这类适合审计存储的系统。你可以看到这个设计并不复杂但它把“能不能调”和“怎么调”完全从模型推理里隔离出来了。6. 用 curl 验证网关的放行与拦截启动服务uvicorn gateway_demo.gateway:app --host 0.0.0.0 --port 8000如果文件路径不在gateway_demo目录下可在启动前调整配置文件路径。启动成功后先发一个合法请求curl -X POST http://127.0.0.1:8000/agent/execute \ -H Content-Type: application/json \ -H Authorization: Bearer demo-secret-token-123 \ -d { intent: 创建一张100元的发票, service: invoice_service, path: /v1/invoices, payload: { customer_id: C001, amount: 100, currency: CNY } }预期返回{ decision: allow, result: { agent: billing-agent, service: invoice_service, path: /v1/invoices, payload: { customer_id: C001, amount: 100, currency: CNY }, backend_result: ok } }这说明合法请求被成功放行。接着尝试访问一个不存在的服务curl -X POST http://127.0.0.1:8000/agent/execute \ -H Content-Type: application/json \ -H Authorization: Bearer demo-secret-token-123 \ -d { intent: 导出用户列表, service: user_service, path: /v1/users/export, payload: {} }预期返回 403{ detail: service or path not allowed }这正是“拒绝默认”策略的效果。即使 Agent 有导出意图如果配置里没有这个服务路径网关也会拦截。再试一个参数错误的请求curl -X POST http://127.0.0.1:8000/agent/execute \ -H Content-Type: application/json \ -H Authorization: Bearer demo-secret-token-123 \ -d { intent: 创建一张非法发票, service: invoice_service, path: /v1/invoices, payload: { customer_id: C002, amount: abc, currency: CNY } }预期返回 422。因为 Schema 里定义了amount必须是数字类型而 Agent 传了字符串。这个校验就是确定性网关最关键的价值之一它不假设 Agent 能生成正确的参数而是假设 Agent 可能出错然后用规则兜底。如果你想让 Agent 在请求被拒绝后自我修正可以把网关返回的错误信息拼回给 Agent让模型看到“参数类型不匹配”后重新生成一次工具调用。这比直接让 Agent 面对一个封闭的后端错误更有效。7. 把网关日志变成 AI Agent 评测的确定性地基最近有个热门词叫“demystifying evals for AI agents”意思是 AI Agent 评测并没有那么神秘但很多团队不知道从哪里测起。如果你的 Agent 只是聊天机器人评测可以看生成文本的相似度但如果 Agent 会调用系统评测就必须包含“工具调用行为”的断言。Stonefold 这类确定性网关天然为 Agent 评测提供了非常扎实的 ground truth。网关日志里记录了每次请求的完整决策你不需要用另一个大模型去判断“这次 Agent 是否越权”你用断言直接比对状态码和日志即可。下面用pytest写一组基于网关接口的 eval 用例。它把评测从“感觉”变成了确定性的资产# tests/test_agent_evals.py from fastapi.testclient import TestClient from gateway_demo.gateway import app client TestClient(app) HEADERS {Authorization: Bearer demo-secret-token-123} def test_allowed_invoice_creation(): response client.post( /agent/execute, json{ intent: 创建一张100元的发票, service: invoice_service, path: /v1/invoices, payload: { customer_id: C001, amount: 100, currency: CNY, }, }, headersHEADERS, ) assert response.status_code 200 assert response.json()[decision] allow def test_unexpected_service_blocked(): response client.post( /agent/execute, json{ intent: 导出所有用户, service: user_service, path: /v1/users/export, payload: {}, }, headersHEADERS, ) assert response.status_code 403 assert response.json()[detail] service or path not allowed def test_wrong_schema_rejected(): response client.post( /agent/execute, json{ intent: 创建一张非法发票, service: invoice_service, path: /v1/invoices, payload: { customer_id: C002, amount: 100, currency: CNY, }, }, headersHEADERS, ) assert response.status_code 422这样一组用例跑完后你的 Agent 评测就有两个层面模型层面评估 Agent 是否选择了正确的服务、路径、参数。系统层面即使模型选择错误网关是否成功拦截。真正进入生产环境的 Agent不应该只依赖“模型层面正确”。系统层面必须有一道确定性防线而这道防线是否有效完全可以用上面这种 eval 用例持续回归。如果你的 Agent 评测还没跑起来我建议从这三步开始先定义 10 个最常见的高风险操作例如删除、导出、改价、转账。为每个操作写一条 eval 用例断言“如果没有授权必须被网关拒绝”。把每条被拒绝的请求写入测试报告形成 Agent 行为基线。这条路径比给模型算一个“平均分”有价值得多。8. 常见问题与排查思路在实际落地确定性网关时遇到的问题往往不在“好不好用”而在“为什么我的请求被拒绝了”。这里整理一份高频问题对照表。问题现象可能原因排查方式解决方案所有请求都返回 401Token 与配置中的 Agent 不匹配检查 Authorization Header 是否正确核对配置里 agents 的 token请求返回 403服务名不在配置中或路径不在白名单内查看网关日志中的拒绝原因增加服务或路径配置并重新发布请求返回 422参数类型、枚举或必填字段不符合 Schema查看网关返回的 detail 字段修正 Agent 的请求参数或调整 Schema同一个请求时好时坏模型生成参数不稳定对比多次请求的 payload用更严格的 Schema 校验并在 Agent 端加入重试机制生产环境很慢Schema 文件每次请求都从磁盘读取或审计日志同步写入慢查看接口耗时和数据库写入延迟缓存 Schema审计日志异步写入Agent 反复重试失败请求模型缺少对错误原因的理解查看返回给 Agent 的错误结构把错误信息拼接成结构化 prompt 片段偶发出现多余字段additionalProperties未开启检查 Schema 配置开启additionalProperties: false需要特别提醒在真实系统中调整网关配置时一定要先在测试环境验证并做好配置备份。任何白名单变更都可能导致 Agent 功能从“可用”变成“不可用”也可能打开一条原本不该打开的风险路径。9. 生产落地建议与安全边界Stonefold 这类确定性网关能不能跑好不完全取决于功能更多地取决于你如何设计规则和运营这套系统。下面这些建议是我在实际项目里见过的最有价值的做法。9.1 默认拒绝而不是默认放行很多人写配置时习惯只把允许的路径列出来剩下的交给后端处理。这在 Agent 场景里风险很高。更安全的策略是默认拒绝只有配置里明确列出的服务、路径、方法才允许请求通过。这样即使模型出现了你完全没见过的调用网关也不会放行。9.2 规则入代码库走评审流程网关的规则不应该在运行时被随意修改而应该以 YAML、JSON 或代码形式纳入版本管理。每次新增路径、放开权限都要像修改业务代码一样做 Code Review。至少保证一条谁改规则谁负责解释这条规则对应的业务场景。9.3 最小权限和独立身份每个 Agent 使用独立的 Token 或身份标识。不要把多个 Agent 共用一个长期凭证。即使两个 Agent 都需要访问订单服务一个只读订单另一个需要创建售后单那么它们的权限范围也应该分开。网关应该支持“身份 服务 路径 方法 参数规则”的多级权限控制。9.4 响应也要做校验确定性网关不仅管请求也建议管响应。后端返回的数据可能包含敏感字段或者体积过大。如果 Agent 被套入一个外部诱导场景它可能把订单列表里的客户手机号返回给用户。网关在把后端响应交给 Agent 前可以做字段脱敏、白名单过滤和体积限制。9.5 幂等与重试策略Agent 经常重试。一个创建发票的请求如果因为网络超时被 Agent 重试两次生产环境可能产生两张重复发票。网关应该支持幂等键检查同一个 Agent、同一个业务操作、同一个幂等键只能成功执行一次。这个能力在写场景非常重要。9.6 审计日志要能支撑溯源审计日志是确定性网关安全价值的一部分。每条日志至少包含请求 ID、Agent ID、目标服务、路径、完整 payload、决策结果、时间戳和关联的后端响应。日志保留周期要符合公司数据管理规范并且建议做冷热分离方便事故追查和合规审计。9.7 用 fake 后端做测评在测试环境里不要直接连接生产数据库或真实供应商接口。搭建一层 fake backend用它来验证网关逻辑也用它来跑 Agent 评测。这样可以在不产生真实业务影响的情况下验证 Agent 的“危险操作是否会被拦截”。10. 结尾与下一步Stonefold 给 AI Agent 工程化提供了一个很清晰的思路想让 Agent 进入生产环境不只要把模型调好更要在模型和系统之间建立一个确定性边界。这个边界可以是 Stonefold也可以是你自己写的十几行规则代码。关键不在于用哪个项目而在于你是否愿意把“安全、权限、审计、错误校验”从模型能力中剥离出来交给确定性的工程系统。如果你正在团队里讨论“要不要给 Agent 加网关”我建议你先别急着争论架构。找一个人用最少代码把带身份认证、白名单路径和 Schema 校验的最小网关跑起来再写一组 eval 用例圈住它。然后你再去对比 Stonefold 这类项目会发现很多设计选择突然就说得通了。真正的 AI Agent 工程化不是让模型拥有越来越多权限而是让模型在可控边界里发挥能力。边界定清楚了Agent 才值得被信任。