企业智能体工程体系v1.1|企业智能体工程卷 · 第4期·Agent-First 工具接口——让 API 为 Agent 可读、可判、可拦
企业智能体工程体系v1.1|企业智能体工程卷 · 第4期
Agent-First 工具接口——让 API 为 Agent 可读、可判、可拦
作者:技术治理研究组
系列:企业智能体工程卷(发布版 v1.1)
主案例:CASE-CR-0042(信用提额申请)
本集对象:ToolSpec · ToolRegistry
协议:承接 P2(工具副作用 ⊆ 契约副作用)
适合读者:架构师、技术负责人、AI 产品经理、企业级 Agent 开发者
📌 本文档声明
- 性质:本文为企业智能体工程化设计参考框架的第 4 期,聚焦 Agent 工具接口的语义化设计,提供架构思路与教学级示意代码,不构成生产级实现方案或法律合规意见。
- 证据锚定:文中案例(CASE-CR-0042)为教学示意,不对应任何真实客户系统。
- 系列定位:本篇在第 1 期(技能契约)的基础上,引入 ToolSpec(工具规格) 作为 Agent 与外部系统交互的标准化接口,并通过 P2 协议确保工具副作用不超出契约边界。
摘要
在前三期我们建立了:
- 第 1 期 SkillContract(技能契约) :Agent“能做什么、不能做什么”
- 第 2 期 决策四轴 + P1:单点决策对齐企业价值
- 第 3 期 DecisionBoard:多环节决策的立场传递与冲突阻断
但还有一个关键问题尚未解决:Agent 怎么调用外部工具/API?
在传统的 API 设计中,接口是“给人看的”——update(id, data) 语义含糊,副作用散落在 Wiki 角落,调用是否合法靠开发者自觉。当 Agent 成为调用者时,这种设计就会成为系统性风险:
- Agent 可能把提额请求写成覆盖整户资料
- Agent 不知道调用这个接口会产生什么副作用
- Agent 的信任门槛靠“提示词里约束”,而非系统强制
本期回答一个核心问题:
如何把工具/API 设计成 Agent 可读、可判、可拦的一等公民?
本期引入 ToolSpec(工具规格) ——将工具的行为名、副作用、信任等级、允许角色和约束声明为结构化对象,并通过 ToolRegistry(工具注册表) 实现调用前的权限、契约和副作用三重校验。
一句话核心:接口要为 Agent 设计——行为名即语义,副作用进签名,调用前可拦截。
1. 问题:为什么“给人看的 API”对 Agent 是灾难
1.1 CASE-CR-0042 中的工具调用
在 CASE-CR-0042 的链路中,Agent 需要调用两个核心工具:
| 环节 | 需要的工具 | 当前 API 形态(问题版) |
|---|---|---|
| 数据 Agent | 查询客户信用信息 | query(customer_id) —— 查什么?返回什么? |
| 财务 Agent | 提交提额裁决 | update(id, data) —— 改了什么?是永久生效吗? |
这些 API 的问题:
| 问题 | 说明 | 对 Agent 的影响 |
|---|---|---|
| 命名含糊 | update、query、process 无法表达业务语义 | Agent 可能调错接口 |
| 副作用隐蔽 | 副作用写在 Wiki 里,不在签名中 | Agent 不知道自己会“闯多大祸” |
| 权限粗放 | 谁都能调,或靠 IAM 粗粒度控制 | 客服 Agent 可能调用财务工具 |
| 信任门槛靠自觉 | “提示词里说了别乱调” | 越狱/注入可绕过 |
1.2 从“给人看”到“给 Agent 看”
| 维度 | 给人看的 API | 给 Agent 看的 API(ToolSpec) |
|---|---|---|
| 命名 | update(id, data) | propose_limit_change(case_id, new_limit) |
| 副作用 | 文档角落 | 签名中的 effects: {limit.write} |
| 授权 | IAM 粗粒度 | allowed_roles: {finance.limit} |
| 约束 | 人工检查 | constraints: policy_range |
| 调用 | 靠自觉 | 调用前三重校验 |
2. ToolSpec:工具即声明
2.1 什么是 ToolSpec
ToolSpec
├── name # 行为化命名,如 "propose_limit_change"
├── effects # 副作用集合,如 {limit.write, email.send}
├── trust_required # 所需信任等级
├── allowed_roles # 允许调用的角色
├── constraints # 调用前约束(政策范围、参数校验)
└── handler # 实际执行函数
核心理念:工具不再是一个“可以调用的函数”,而是一个带有完整语义声明的可执行规格。
2.2 ToolSpec 与 SkillContract 的对扣关系
| SkillContract(技能契约) | ToolSpec(工具规格) |
|---|---|
| 声明 Agent 能做什么 | 声明工具有什么副作用 |
side_effects: {limit.write} | effects: {limit.write} |
P2 裁决:Tool 的 effects 必须被当前 Skill 的 side_effects 覆盖 |
3. CASE-CR-0042 的两个工具
3.1 工具一:只读查询
| 属性 | 值 |
|---|---|
| name | get_credit_snapshot |
| effects | ∅(只读,无副作用) |
| trust_required | 1(低信任) |
| allowed_roles | {data.credit, support.intake} |
| 约束 | 仅 case 绑定客户 |
ToolSpec(
name="get_credit_snapshot",
effects=frozenset(), # 只读
trust_required=1,
allowed_roles=frozenset({"data.credit", "support.intake"}),
handler=get_credit_snapshot,
)
3.2 工具二:写操作(提额)
| 属性 | 值 |
|---|---|
| name | propose_limit_change |
| effects | {limit.write} |
| trust_required | 2(高信任) |
| allowed_roles | {finance.limit}(客服不可调用) |
| 约束 | 目标额度 ∈ 政策区间;须带 case_id |
ToolSpec(
name="propose_limit_change",
effects=frozenset({"limit.write"}), # 写操作,副作用显式声明
trust_required=2,
allowed_roles=frozenset({"finance.limit"}),
handler=propose_limit_change,
)
3.3 角色裁剪
| 角色 | 可调用的工具 |
|---|---|
support.intake | get_credit_snapshot(只读) |
data.credit | get_credit_snapshot(只读) |
finance.limit | get_credit_snapshot + propose_limit_change |
客服 Agent 在 SkillContract 层面就没有 limit.decide 契约,在 ToolRegistry 层面也没有 propose_limit_change 工具的授权。双重保险。
4. 最小代码:ToolRegistry + P2 校验
以下为教学级示意代码,展示 ToolSpec 的定义与 ToolRegistry 的调用前三重校验:
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Callable
@dataclass(frozen=True)
class ToolSpec:
"""工具规格——声明工具的语义、副作用和授权边界。"""
name: str
effects: frozenset[str] # 副作用集合
trust_required: int # 所需信任等级
allowed_roles: frozenset[str] # 允许调用的角色
handler: Callable[..., Any] # 实际执行函数
class ToolRegistry:
"""工具注册表——调用前执行三重校验。"""
def __init__(self) -> None:
self._tools: dict[str, ToolSpec] = {}
def register(self, spec: ToolSpec) -> None:
"""注册一个工具。"""
self._tools[spec.name] = spec
def call(
self,
name: str,
*,
role: str,
trust_level: int,
skill_effects: set[str], # 当前技能契约的副作用
requested_effects: set[str], # 调用方声明的副作用
**kwargs: Any,
) -> Any:
"""
调用工具——三重校验。
校验 1:角色授权
校验 2:信任等级
校验 3:P2——工具副作用 ⊆ 技能契约副作用
"""
tool = self._tools[name]
# 校验 1:角色授权
if role not in tool.allowed_roles:
raise PermissionError(
f"[ToolRegistry] {name}: 角色 {role} 未授权"
)
# 校验 2:信任等级
if trust_level < tool.trust_required:
raise PermissionError(
f"[ToolRegistry] {name}: 信任不足 (需要 {tool.trust_required}, 当前 {trust_level})"
)
# 校验 3:P2——工具副作用必须 ⊆ 技能契约副作用
if not tool.effects.issubset(skill_effects):
raise PermissionError(
f"[ToolRegistry] P2: {name} 的副作用 {sorted(tool.effects)} "
f"超出技能契约 {sorted(skill_effects)}"
)
# 校验 4:调用方必须声明所有副作用
if tool.effects and not tool.effects.issubset(requested_effects):
raise PermissionError(
f"[ToolRegistry] {name}: 须声明副作用 {sorted(tool.effects)}"
)
# 校验 5:不能声明超出工具的副作用
unknown = requested_effects - tool.effects
if unknown:
raise PermissionError(
f"[ToolRegistry] {name}: 声明了未知副作用 {sorted(unknown)}"
)
# 全部通过 → 执行
return tool.handler(**kwargs)
# ===== 工具实现 =====
def get_credit_snapshot(case_id: str, customer_id: str) -> dict[str, Any]:
"""获取客户信用快照(只读)。"""
assert case_id == "CASE-CR-0042"
return {
"customer_id": customer_id,
"current_limit": 50000,
"requested": 120000,
"debt_trend": "up",
"score": 62,
}
def propose_limit_change(case_id: str, new_limit: int) -> dict[str, Any]:
"""提议额度变更(写操作)。"""
# 约束:政策区间
if not (50000 <= new_limit <= 150000):
raise ValueError(f"额度 {new_limit} 超出政策区间 (50,000 ~ 150,000)")
return {
"case_id": case_id,
"proposed": new_limit,
"status": "pending_audit",
}
# ===== 运行演示:CASE-CR-0042 =====
if __name__ == "__main__":
registry = ToolRegistry()
# 注册工具
registry.register(
ToolSpec(
name="get_credit_snapshot",
effects=frozenset(),
trust_required=1,
allowed_roles=frozenset({"data.credit", "support.intake"}),
handler=get_credit_snapshot,
)
)
registry.register(
ToolSpec(
name="propose_limit_change",
effects=frozenset({"limit.write"}),
trust_required=2,
allowed_roles=frozenset({"finance.limit"}),
handler=propose_limit_change,
)
)
print("=== 场景 1:数据 Agent 只读查询(通过) ===")
result = registry.call(
"get_credit_snapshot",
role="data.credit",
trust_level=1,
skill_effects=set(), # 只读技能,无副作用
requested_effects=set(),
case_id="CASE-CR-0042",
customer_id="星河零售",
)
print(f" ✅ 结果: {result}")
print("\n=== 场景 2:财务 Agent 提额(通过) ===")
result = registry.call(
"propose_limit_change",
role="finance.limit",
trust_level=2,
skill_effects={"limit.write"}, # 技能契约声明了写额度
requested_effects={"limit.write"},
case_id="CASE-CR-0042",
new_limit=90000,
)
print(f" ✅ 结果: {result}")
print("\n=== 场景 3:客服 Agent 尝试提额(拦截:角色未授权) ===")
try:
registry.call(
"propose_limit_change",
role="support.intake",
trust_level=2,
skill_effects={"limit.write"},
requested_effects={"limit.write"},
case_id="CASE-CR-0042",
new_limit=120000,
)
except PermissionError as e:
print(f" ❌ 拦截: {e}")
print("\n=== 场景 4:契约未声明副作用(拦截:P2) ===")
try:
registry.call(
"propose_limit_change",
role="finance.limit",
trust_level=2,
skill_effects=set(), # 技能契约未声明 limit.write
requested_effects={"limit.write"},
case_id="CASE-CR-0042",
new_limit=90000,
)
except PermissionError as e:
print(f" ❌ 拦截: {e}")
print("\n=== 场景 5:声明了工具没有的副作用(拦截) ===")
try:
registry.call(
"get_credit_snapshot",
role="data.credit",
trust_level=1,
skill_effects=set(),
requested_effects={"limit.write"}, # 工具是只读,不应声明写
case_id="CASE-CR-0042",
customer_id="星河零售",
)
except PermissionError as e:
print(f" ❌ 拦截: {e}")
运行输出:
=== 场景 1:数据 Agent 只读查询(通过) ===
✅ 结果: {'customer_id': '星河零售', 'current_limit': 50000, 'requested': 120000, 'debt_trend': 'up', 'score': 62}
=== 场景 2:财务 Agent 提额(通过) ===
✅ 结果: {'case_id': 'CASE-CR-0042', 'proposed': 90000, 'status': 'pending_audit'}
=== 场景 3:客服 Agent 尝试提额(拦截:角色未授权) ===
❌ 拦截: [ToolRegistry] propose_limit_change: 角色 support.intake 未授权
=== 场景 4:契约未声明副作用(拦截:P2) ===
❌ 拦截: [ToolRegistry] P2: propose_limit_change 的副作用 ['limit.write'] 超出技能契约 []
=== 场景 5:声明了工具没有的副作用(拦截) ===
❌ 拦截: [ToolRegistry] get_credit_snapshot: 声明了未知副作用 ['limit.write']
5. 三个教训
基于 CASE-CR-0042 的 ToolSpec 设计经验:
| 教训 | 含义 | 证据 |
|---|---|---|
| 命名即行为 | propose_limit_change 优于 update——Agent 从名称即可理解工具的业务语义 | 场景 2 vs 传统 update |
| 副作用是签名的一部分 | 调用前即可检查工具的副作用,而非运行时才发现“闯祸了” | 场景 4:P2 拦截契约未声明副作用 |
| 工具注册表按角色裁剪 | 比“全员可见 API 目录”更安全——客服 Agent 从工具列表中就看不到提额工具 | 场景 3:客服调用被拦截 |
6. 思考题
以下问题供团队内部讨论,帮助将 ToolSpec 概念落地到具体场景:
-
行为化命名:在 CASE-CR-0042 上,还有哪个工具需要行为化命名?例如补件通知工具应该叫什么?
send_document_request还是notify_missing_docs? -
副作用发现:若
get_credit_snapshot被运维人员“顺手”加上了缓存写副作用(cache.write),Skill 与 Tool 谁先改?P2 会如何拦截? -
角色裁剪粒度:
propose_limit_change当前只允许finance.limit调用。如果需要支持“财务实习生”角色(可提交但需二审),应该如何处理?是扩展现有角色还是新增工具?
7. 下期预告
第 5 期:数据飞轮 + P3
误分类工单类型时,如何通过数据回流形成改进闭环。引入 P3 协议:Plan 未过 Assurance 不得进生产。
8. 延伸阅读
| 资源 | 说明 |
|---|---|
| Agent-First Tool API: A Semantic Interface Paradigm for Enterprise AI Agents(arXiv:2605.10555) | Agent-First 工具接口设计框架 |
| Contractual Skills: A GovernSpec Design Framework for Enterprise AI Agents(arXiv:2605.22634) | 技能契约设计框架 |
| 本卷第 1 期:技能即契约——SkillContract + P2 | 能力边界契约化 |
| 本卷第 2 期:决策四轴——四轴 + P1 | 单点决策对齐 |
| 本卷第 3 期:无状态决策记忆——DecisionBoard | 多环节决策传递 |
本文是「企业智能体工程卷」十期专栏的第 4 期。Agent-First 工具接口——让 API 为 Agent 可读、可判、可拦,让工具从“给开发者看的”变成“给 Agent 看的一等公民”。欢迎转载,请注明出处与原文标题。
更多推荐




所有评论(0)