
AI Agent 与后端系统之间的对接正在从“写一个接口让 Agent 调用”升级为“在 Agent 和系统之间放一层确定性网关”。Stonefold 在 Show HN 上的定位正是 deterministic gateway between AI agents and your systems。这里的关键词不是“AI”而是“deterministic”大模型本身不可预测但系统边界必须可预测。所谓确定性网关就是所有从 Agent 到业务系统的工具调用都要经过一套固定契约——工具可枚举、参数有 Schema、权限有策略、错误有标准格式、调用有完整痕迹。这个模式解决的是三类真实问题第一Agent 会以多种方式尝试同一个目标可能生成非法参数、重复调用、越权操作第二业务系统普遍不具备为 Agent 设计容错的能力如果后端接口被畸形容错打穿出问题的不是模型而是你的系统第三没有统一网关Agent 的行为几乎无法评估、回放和复盘。下面先拆解确定性网关的工作机制再给出一个最小可运行示例最后讲清排错链路、评测方法和生产落地建议。如果你正在做 Agent 工具接入、企业级 Assistant 或自动化运维机器人这篇文章会提供一套可直接迁移的边界治理方案。1. 为什么 AI Agent 与系统之间需要一层确定性网关1.1 LLM 的非确定性让“系统接入”变成治理问题传统 API 对接是确定性的客户端是自己的代码参数类型由编译期或测试期保证接口文档描述即约束。AI Agent 完全不同Agent 的每一次工具调用都由大模型生成同一个问题在不同温度、不同 prompt、不同模型版本下可能产生不同的工具选择、参数顺序、甚至完全错误的后端方法名。举个例子。你给 Agent 暴露了一个 create_order(orderId, amount, userId) 接口期望参数是数字。Agent 可能传入字符串 100.5把 orderId 和 userId 顺序颠倒在参数里混入多余字段对同一个接口连续调用三次只因为第一次响应超时试图调用一个根本没注册过的工具 guess_discount。这些行为在人类客户端中几乎不可能批量出现但在 Agent 调用中却是常态。如果一个系统不做任何边界约束就把这些请求透传给数据库或订单服务轻则产生脏数据重则重复扣款或越权读取。这就需要把“系统接入”问题转化为“治理问题”不是祈祷模型别犯错而是假设模型一定犯错然后在系统边界统一拦截、纠正、记录。1.2 确定性网关的定位契约不变、行为可预期确定性网关是一种反向代理式中间层但它代理的对象不是用户浏览器而是 Agent 输出的工具调用。它并不试图让 LLM 的文本输出变得确定这不可能也不必要它要做的是让“Agent 与系统之间的那一段”确定下来。这段边界包括四个约定工具注册表固定Agent 只能调用注册过的工具每个工具都带输入 Schema 和描述参数格式固定进入网关的每个工具调用先做 Schema 校验不合法的直接返回结构化错误权限策略固定Agent 身份、工具、字段和值域之间的授权关系由策略引擎决定不写在业务代码里响应和错误格式固定后端正常返回和异常返回都统一成 Agent 可解析的格式。这样无论底层模型怎么变业务系统看到的请求永远符合契约无论后端怎么报错Agent 拿到的错误都能指导它下一步行动。这就是 deterministic gateway 的核心含义系统对你负责你对模型负责中间层负责把不确定性消化掉。1.3 与普通 API 网关的区别很多人会问Kong、APISIX、Spring Cloud Gateway 不是早就做网关了吗确实现有网关能处理认证、限流、路由但它们的设计假设是“调用方是可靠的程序”。确定性网关的差异在于它必须处理维度普通 API 网关Agent 确定性网关调用方确定的程序客户端非确定的 LLM 输出请求来源一次请求一次决策多轮循环、自动重试、并行工具调用参数质量基本可信做常规校验需要强 Schema 校验和值域约束错误设计面向人阅读面向 Agent 自动恢复必须结构化日志价值排错为主排错之外还要用于 eval 和回放权限粒度多按用户或应用还需按 Agent、会话、工具、字段分层所以常见的做法不是拿普通网关硬改成 Agent 网关而是在现有 API 网关后面或旁边单独加一层“工具调用网关”。Stonefold 这类项目的价值正在于把这一层从业务代码里抽出来变成独立可配置、可审计、可评测的基础设施。2. 先拆解确定性网关的五个核心部件2.1 工具注册表一切能力先上契约网关的起点是一份工具注册表Tool Registry。它描述 Agent 可以使用哪些能力每个能力长什么样。一份典型契约包含工具名、用途描述、输入 Schema、输出 Schema、是否幂等、超时和重试策略。为什么 Schema 要单独出现因为 LLM 天然擅长理解自然语言描述但在数值、枚举、必填字段上经常出错。Schema 把“参数必须长什么样”变成机器可校验的规则而不是让后端业务代码去猜测。一个最小工具契约示例如下{ tool: create_issue, description: 在项目管理系统中创建一条任务, input_schema: { type: object, properties: { project_key: { type: string, enum: [OPS, PAY, DATA] }, title: { type: string, minLength: 3, maxLength: 80 }, priority: { type: string, enum: [P0, P1, P2], default: P2 } }, required: [project_key, title] }, idempotent: false, timeout_ms: 3000 }这个契约同时做了三件事一是让 Agent 的 system prompt 可以引用它指导模型生成合法参数二是让网关能机检参数三是让后端团队明确自己提供的接口边界。注意enum和default非常关键它们能直接减少模型在自由输入上的发挥空间。2.2 校验层把自由文本变成结构化参数校验层负责执行 Schema 校验。它不只是“过一遍 JSON Schema”还包含类型强制、字段裁剪、默认值填充和非法字段剔除。这里有一个取舍是拒绝非法参数还是自动纠偏推荐分两种情况处理值域错误枚举外、类型错误直接拒绝返回 UNKNOWN_ENUM 或 TYPE_MISMATCH 这类结构化错误可安全补齐的字段有 default、可空填充默认值后继续。不要对 Agent 的请求做过度“修正”。如果网关把任何非法参数都悄悄修好Agent 会学到“随便传也能成功”真实错误反而被掩盖。确定性网关的意义之一就是让模型收到明确的失败信号从而在下一轮自行纠正。2.3 策略层权限、限流、预算和开关策略层决定“这个 Agent 能不能调用这个工具、能调用多频繁、能消耗多少资源”。它应该独立于业务代码用配置或策略文件表达方便安全和运维同学 review。一个最小策略文件示例agents: ops-bot: enabled: true allowed_tools: - create_issue - query_order rate_limit: calls_per_minute: 30 max_calls_per_session: 200 allowed_project_keys: - OPS - DATA策略层的价值是紧急止血。当线上出现 Agent 反复调用某一个昂贵接口时最快的手段不是改代码而是把这个 Agent 或工具在策略里临时禁用。没有这一层Agent 会把一个小问题放大成成本或故障事故。2.4 执行与归一化层后端错误也要有固定格式执行层负责真正调用后端系统。它要处理连接、超时、重试和幂等并把业务系统千奇百怪的异常统一成 Agent 可读的响应格式{ status: error, error: { code: BACKEND_TIMEOUT, message: create_issue 调用超过 3000ms, retryable: true } }不要把后端原始异常文本原样抛给 Agent。后端异常是为工程师设计的里面可能有堆栈、内部路径、SQL 片段而 Agent 需要的是“能否重试”“参数哪里错了”“下一步怎么办”。归一化层可以去掉敏感信息同时保留错误分类信息让 Agent 在retryabletrue时走重试分支在retryablefalse时换一种策略。2.5 痕迹层每个调用都要可回放痕迹层是确定性网关最容易忽略、却也最重要的部分。每次工具调用都应记录会话 ID、Agent 身份、工具名、参数摘要、校验结果、策略决策、后端响应、耗时、最终结果哈希。这些痕迹有三个用途。第一是安全审计确认 Agent 是否越权、是否碰了不该碰的数据第二是排错复现“Agent 为什么调了这个接口”第三是 eval 数据底座后面会单独展开。痕迹不是普通 access log它必须能被按会话或按 trace 聚合以支持事件回放。3. 用最小示例跑通校验、放行与拒绝三种路径3.1 最小架构和学习环境为了把上面的概念落地这里用一个最小 Python 示例模拟确定性网关。它只包含注册表、校验、策略、执行、响应五个函数没有任何框架依赖。学习时可以直接在本地运行python gateway_demo.py需要 Python 3.9 以上并安装jsonschemapip install jsonschema这个示例不是为了替代生产框架而是为了让你理解一个工具调用从进入网关到返回 Agent经历了哪些判断点、每个判断点返回什么错误。3.2 定义工具契约先放一份工具契约这里把工具保存在 Python 字典中TOOL_CONTRACTS { create_issue: { description: 创建一条项目任务, input_schema: { type: object, properties: { project_key: { type: string, enum: [OPS, PAY, DATA] }, title: { type: string, minLength: 3, maxLength: 80 }, priority: { type: string, enum: [P0, P1, P2], default: P2 } }, required: [project_key, title] } } }注意enum和default是约束 Agent 自由度的主要手段。default可以在模型漏传时自动补齐enum则可以把“优先级”这类开放输入收敛成固定集合。3.3 网关处理函数下面是网关的核心处理函数from jsonschema import validate, ValidationError class BackendTimeout(Exception): pass class BackendError(Exception): pass def err(code, message, retryableFalse): return { status: error, error: { code: code, message: message, retryable: retryable, }, } def gateway(agent_id, tool_call, registry, policy): tool_name tool_call.get(name) arguments tool_call.get(arguments, {}) # 1. 工具是否存在 if tool_name not in registry: return err(UNKNOWN_TOOL, ftool {tool_name} not registered) contract registry[tool_name] # 2. 策略是否允许 if not policy.is_allowed(agent_id, tool_name): return err(POLICY_DENIED, fagent {agent_id} cannot call {tool_name}) # 3. Schema 校验 try: validate(instancearguments, schemacontract[input_schema]) except ValidationError as exc: return err(SCHEMA_INVALID, exc.message) # 4. 执行后端 try: