SAP 先把 Agent 里无聊的那一半交付了:网关、注册表,以及「未验证的 Agent 不许被调用」
SAP 在 10 月 28 日的 TechEd 主题演讲里,几乎没有花时间讲模型质量,而是把大量时间花在没人愿意做演示的那些部分:网关、注册表、验证闸门、审计链。这个直觉是对的,而且即使你一辈子不碰 SAP 也值得细读。标题级的说法是:Joule Studio 把从「说清想要什么」到「Agent 可用」的时间从 13 小时压到约 2.5 小时;因为 Agent 不用再猜请求、token 用得更少,单任务成本降低约 50%;SAP 与合作伙伴合计交付 40 个助手与 300 个 Agent;以及运行时强制——凡未在 SAP AI Agent Hub 里验证过的 Agent 无法被调用。前两个是营销。第三个是一个值得你这个季度就抄走的架构决定。
Agent gateway 今天已正式可用,Enterprise AI Gateway 叠在它之上。
一、这些数字,量的到底是什么
40 个助手与 300 个 Agent 是目录数量,SAP 也明说这些是在 SAP 与合作伙伴之间分摊的。每一个都被描述为符合 ISO 与 SOC,这是关于工作负载周边控制措施的声明,而不是关于 Agent 能力水平的声明。比较有意思的是时间数字:2.5 小时对 13 小时,单任务成本低 50%,原因是 Agent 用的 token 更少——因为 Process Consulting Agent 先把「该建什么」定下来,SAP Knowledge Graph 找到相关的 API、MCP Server 与数据产品,Joule Studio 再生成规格、结构与集成逻辑。这句话该读成「消除歧义」的声明,而不是「模型跑得快」的声明。那 13 小时里大部分从来不是算力,是搞清到底该调哪个端点。
# The registry record. Everything else in this pattern reads from here.
# Keep it boring and keep it in a database, not in a spreadsheet.
import sqlite3
SCHEMA = """
CREATE TABLE IF NOT EXISTS agents (
agent_id TEXT PRIMARY KEY,
owner TEXT NOT NULL,
version TEXT NOT NULL,
capabilities TEXT NOT NULL, -- JSON array of declared capability names
scopes TEXT NOT NULL, -- JSON array of granted scopes
endpoint TEXT NOT NULL,
verified_at TEXT, -- ISO 8601, NULL means never verified
verified_by TEXT,
expires_at TEXT -- verification is not permanent
);
CREATE TABLE IF NOT EXISTS invocations (
trace_id TEXT PRIMARY KEY,
agent_id TEXT NOT NULL,
version TEXT NOT NULL,
caller TEXT NOT NULL,
scopes_used TEXT NOT NULL,
outcome TEXT NOT NULL,
started_at TEXT NOT NULL,
FOREIGN KEY (agent_id) REFERENCES agents(agent_id)
);
"""
def init(path="agents.db"):
con = sqlite3.connect(path)
con.executescript(SCHEMA)
con.commit()
return con
# Note the two tables together. The registry alone is a spreadsheet with
# extra steps. The registry plus the invocation log is an audit trail, and
# the audit trail is what a risk classification is built from later.二、一个网关今天可用,上面还叠了一个更大的
SAP 说它的 agent gateway 今天已正式可用,可以跨任意系统边界连接 Agent 与端点。针对需要限流、路由、协议转换与高级访问控制的场景,它发布了 SAP Enterprise AI Gateway,构建在 SAP Integration Suite 之上,是叠加而不是替换。这个形状有两点值得注意。第一,网关被定位成「让每个新平台不再需要定制集成」的那一层——在一个有几十个数据源的系统里,定制集成才是真正的成本驱动。第二,SAP 说它交付的每个 Agent 原生就是 A2A 端点。这跟 Agent2Agent 规范的姿态一致:Agent 的契约是一张公开的 Card,而不是一个私有 SDK,于是网关可以按能力而不是按厂商来路由。你在做内部 Agent 的话,发布 Card 并在前面放一个网关,是最便宜的「可迁移保险」。
# Serve the record as an agent card so a gateway can route on capability
# rather than on vendor. This is the A2A-style contract SAP describes.
import json
import sqlite3
def agent_card(con, agent_id: str) -> dict:
row = con.execute(
"SELECT agent_id, owner, version, capabilities, scopes, endpoint,"
" verified_at, expires_at FROM agents WHERE agent_id = ?",
(agent_id,),
).fetchone()
if row is None:
raise KeyError(agent_id)
return {
"agent_id": row[0],
"owner": row[1],
"version": row[2],
"capabilities": json.loads(row[3]),
"scopes": json.loads(row[4]),
"endpoint": row[5],
"verification": {
"verified_at": row[6],
"expires_at": row[7],
"status": "verified" if row[6] else "unverified",
},
# A card that does not say how to call it is documentation, not a contract.
"protocol": "a2a/1.0",
"content_types": ["application/json"],
}
# Publish at /.well-known/agent/{agent_id} or whatever your gateway reads.
# The point is that the gateway never needs a bespoke integration: it reads
# the card, matches the capability the caller asked for, and routes.三、运行时验证强制才是真正的发布
要划线的那句话是:运行时验证强制确保未在 SAP AI Agent Hub 中验证过的 Agent 无法被调用。这不是一份制度文件,这是调用链上的一个闸门,而且它把常见的企业模式反过来了——通常治理是上线前的一次评审,之后就不再起作用。它同时给了治理叙事一个机制。SAP AI Agent Hub 首席产品经理 Michelle Niedernhuber 把设计目标说得很准:需要把定义、评估、验证 Agent 的设计期层,与执行、监控、优化其行为的运行时层连起来。她那句总结值得引用,因为它正好和治理通常被推销的方式相反:治理不是拖慢 AI 采用的东西,它是让 AI 能规模化采用的东西。这套机制能不能扛住真实的变更评审委员会是另一个问题,但机制本身是站得住的。
# The gate. This is the whole announcement: unverified means uninvocable,
# enforced in the call path rather than in a review document.
from datetime import datetime, timezone
class UnverifiedAgent(Exception):
pass
def call_agent(con, agent_id: str, caller: str, scope: str, payload: dict, dispatch):
row = con.execute(
"SELECT version, scopes, verified_at, expires_at FROM agents WHERE agent_id = ?",
(agent_id,),
).fetchone()
if row is None:
raise UnverifiedAgent(f"{agent_id}: not in registry")
version, scopes, verified_at, expires_at = row
if not verified_at:
raise UnverifiedAgent(f"{agent_id}: never verified")
now = datetime.now(timezone.utc)
if expires_at and datetime.fromisoformat(expires_at) <= now:
raise UnverifiedAgent(f"{agent_id}: verification expired at {expires_at}")
if scope not in scopes_json(scopes):
raise PermissionError(f"{agent_id}: scope {scope!r} not granted")
trace_id = new_trace_id()
try:
result = dispatch(payload)
except Exception as exc:
log(con, trace_id, agent_id, version, caller, scope, "error")
raise
log(con, trace_id, agent_id, version, caller, scope, "ok")
return {"trace_id": trace_id, "result": result}
# Two details that decide whether this survives a real incident review:
# 1. Verification has an expiry. A verification from eighteen months ago
# is a historical fact, not a current guarantee.
# 2. The trace_id is returned to the caller. Without it, the log exists but
# nobody can join it to the request that went wrong.四、欧盟 AI 法案在产品路线图里长什么样
在欧盟 AI 法案已生效的背景下,SAP 说将在 11 月向 Early Adopter Care 客户发布 AI Governance Assistant。描述出来的行为很具体、很不性感:它使用客户已经掌握的信息,引导 Agent 负责人回答剩下的问题,产出一份可直接用于审计的 AI 风险分级。这个分工是对的。分级本质上是一张要填的表,模型擅长填表;而决定分级的事实是组织内部信息,只有负责人手里有。同一节还提到 agent observability 与 SAP Signavio agent mining,把 Agent 设计与生产行为之间的环路闭上。如果你要把 Agent 送进欧盟,现在实际要做的活儿是:确保那位助手会索要的证据,已经以机器可读的形式存在——因为给一个没有调用日志的系统事后补风险分级,是一个季度的工作量。
# Rate limiting and budget at the gateway, not in the agent. An agent that
# polices its own spending is an agent that can be talked out of it.
import time
from collections import defaultdict, deque
class TokenBucket:
def __init__(self, per_minute: int):
self.capacity = per_minute
self.tokens = float(per_minute)
self.rate = per_minute / 60.0
self.updated = time.monotonic()
def take(self, n: float = 1.0) -> bool:
now = time.monotonic()
self.tokens = min(self.capacity, self.tokens + (now - self.updated) * self.rate)
self.updated = now
if self.tokens < n:
return False
self.tokens -= n
return True
LIMITS = defaultdict(lambda: TokenBucket(60)) # per agent_id
DAILY_TOKEN_BUDGET = defaultdict(int)
def admit(agent_id: str, estimated_tokens: int) -> tuple[bool, str]:
if not LIMITS[agent_id].take():
return False, "rate limited"
if DAILY_TOKEN_BUDGET[agent_id] + estimated_tokens > 2_000_000:
return False, "daily token budget exhausted"
DAILY_TOKEN_BUDGET[agent_id] += estimated_tokens
return True, "admitted"
# Why here and not in the agent: the agent's context can be influenced by the
# content it processes. The gateway's cannot. Put every number that must not
# be negotiable outside the thing that can be persuaded.五、skill 抽象是在押注非开发者,而且有代价
Joule Studio 的主打抽象是 skill:一段自然语言描述加几行代码,声明一个 Agent 该做什么。SAP 说这让 Agent 构建不再局限于开发者。重新设计的前端 Joule Work 已被超过 11 万名 SAP 员工以及 Early Adopter Care 计划中 150 多家客户测试过,开箱即连 SAP 各类应用以及 Office365、Claude Cowork、Glean、ServiceNow。这套说法有它的道理:知道流程该干什么的人,通常不是能写集成的人,把定义前移到业务专家那里,能省掉一个历史上最主要的返工来源。代价是自然语言描述不像代码那样可测。你要采用这个模式,就必须给 skill 描述配一条验收测试,否则这个抽象只是把模糊性挪到了更看不见的地方。
# Protocol translation at the gateway: expose one internal contract and
# translate to whatever the endpoint actually speaks. This is what stops a
# bespoke integration per platform from accumulating.
import json
def to_openai_chat(request: dict) -> dict:
return {
"model": request["model"],
"messages": [{"role": m["role"], "content": m["text"]} for m in request["messages"]],
"max_tokens": request.get("max_output_tokens", 4096),
"temperature": request.get("temperature", 1.0),
}
def to_mcp_tool_call(request: dict) -> dict:
return {
"jsonrpc": "2.0",
"id": request["trace_id"],
"method": "tools/call",
"params": {"name": request["capability"], "arguments": request["arguments"]},
}
def to_a2a_task(request: dict) -> dict:
return {
"task": {
"id": request["trace_id"],
"message": {"role": "user", "parts": [{"text": request["prompt"]}]},
}
}
ADAPTERS = {
"openai-chat": to_openai_chat,
"mcp": to_mcp_tool_call,
"a2a": to_a2a_task,
}
def translate(request: dict) -> dict:
target = request["protocol"]
if target not in ADAPTERS:
raise ValueError(f"unsupported protocol: {target}")
return ADAPTERS[target](request)
# Keep the internal contract narrow on purpose. Every field you allow into it
# is a field every adapter must handle, forever.六、不买平台,怎么抄这个模式
三块可以拆开,每块都不大。注册表:一张表,字段是 agent id、负责人、版本、声明的能力、授权范围、验证状态与验证时间。Card 端点:把这条记录以 JSON 形式发布在一个固定路径上,好让网关按它路由。闸门:调用链里的一个函数,读注册表,凡是拿不出新鲜验证记录的一律拒绝。再加一份调用日志,含 trace id、agent id、版本、调用方、实际用到的授权范围与结果——正是这份日志让日后做风险分级与审计链成为可能。这些都不需要厂商,需要两个工程师,以及一个决定:未验证即不可调用,写在代码里而不是写在 wiki 上。最后这一点才是整场发布的全部内容。
运行时验证强制:没有验证记录,就没有调用。
设计期监督与运行时强制,靠一个注册表连起来。
📌 常见问题 FAQ
SAP 的 agent gateway 正式可用了吗?
是。SAP 在 TechEd 2026 上说 agent gateway 自 2026 年 10 月 28 日起正式可用,可跨系统边界连接 Agent 与端点。构建在 SAP Integration Suite 之上的 SAP Enterprise AI Gateway 则是用于限流、路由、协议转换与高级访问控制的叠加层。
运行时验证强制到底做什么?
它拦住调用。未在 SAP AI Agent Hub 中验证过的 Agent 无法在运行时被调用,这把治理从「上线前的一份评审文档」变成调用链上的一个闸门。
SAP 一共宣布了多少 Agent 与助手?
40 个助手与 300 个 Agent,在 SAP 与合作伙伴之间分摊。SAP 称每一个都符合 ISO 与 SOC,并且每一个原生就是通过其 A2A gateway 连接的 A2A 端点。
2.5 小时对 13 小时这个数字怎么来的?
SAP 归因于 Joule Studio 加上 Process Consulting Agent 与 SAP Knowledge Graph:在生成任何 Agent 之前先确定该建什么、并定位相关的 API、MCP Server 与数据产品。声称的效果是单任务成本低约 50%,因为请求不再含糊时 Agent 用的 token 更少。
不买 SAP 也能复刻这套治理模式吗?
可以,而且很便宜:一张 Agent 注册表、一个把注册记录以 JSON 发布的 card 端点、一个在调用链里拒绝未验证 Agent 的验证闸门,以及一份记录 trace id、版本、授权范围与结果的调用日志。治理价值来自闸门是用代码强制执行。