企业智能体工程体系v1.1|企业智能体工程卷 · 第4期

Agent-First 工具接口——让 API 为 Agent 可读、可判、可拦

作者:技术治理研究组
系列:企业智能体工程卷(发布版 v1.1)
主案例:CASE-CR-0042(信用提额申请)
本集对象:ToolSpec · ToolRegistry
协议:承接 P2(工具副作用 ⊆ 契约副作用)
适合读者:架构师、技术负责人、AI 产品经理、企业级 Agent 开发者

📌 本文档声明

  1. 性质:本文为企业智能体工程化设计参考框架的第 4 期,聚焦 Agent 工具接口的语义化设计,提供架构思路与教学级示意代码,不构成生产级实现方案或法律合规意见。
  2. 证据锚定:文中案例(CASE-CR-0042)为教学示意,不对应任何真实客户系统。
  3. 系列定位:本篇在第 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 的影响
命名含糊updatequeryprocess 无法表达业务语义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 工具一:只读查询

属性
nameget_credit_snapshot
effects(只读,无副作用)
trust_required1(低信任)
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 工具二:写操作(提额)

属性
namepropose_limit_change
effects{limit.write}
trust_required2(高信任)
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.intakeget_credit_snapshot(只读)
data.creditget_credit_snapshot(只读)
finance.limitget_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 概念落地到具体场景:

  1. 行为化命名:在 CASE-CR-0042 上,还有哪个工具需要行为化命名?例如补件通知工具应该叫什么?send_document_request 还是 notify_missing_docs

  2. 副作用发现:若 get_credit_snapshot 被运维人员“顺手”加上了缓存写副作用(cache.write),Skill 与 Tool 谁先改?P2 会如何拦截?

  3. 角色裁剪粒度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 看的一等公民”。欢迎转载,请注明出处与原文标题。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐