给 Agent 安装技能:工具抽象、自动选工具与安全边界

我们已经搞定了:

  • 概念篇:把"Agent"和"会聊天的 LLM / RAG"区分开;
  • 架构篇:用 5 个核心组件(意图理解 / 规划 / 执行 / 状态 / 评估)搭出一套通用 Agent 骨架。

这一篇,我们专门盯住其中最关键、也最容易踩坑的一层:

工具层(Tools / Actions)​

------ 也就是「给 Agent 安装技能」的地方。

因为在工程实践里,Agent 之所以有价值,本质上不是"会说",而是:

  • 能查业务数据(订单、工单、用户画像);
  • 能调内部系统(CI/CD、告警、自愈、运维平台);
  • 能把结论写回系统(发通知、建工单、改配置草案)。

这些"能做什么",全部都要通过受控的工具抽象暴露给 Agent。


一、为什么要专门设计"工具层",而不是随便调 API?

如果你直接在 Prompt 里写:

"你可以自由调用公司内部的任何 HTTP 接口......"

结果大概率会是:

  • 安全风险:一不小心就调用了"删库 / 重启 / 改配置"这类高危操作;
  • 行为不可控:你根本不知道它具体调了什么、参数是啥;
  • 无法演进:每多一个接口就多一堆 Prompt 拼接逻辑,后期维护困难。

我自己的经验是:

要把"Agent 能干的事"收敛到一层统一的工具抽象里,让业务 API / 外部系统都变成一个个受控的 Tool。

可以用一句话概括这层设计目标:

所有可执行能力都必须通过 Tool 白名单显式暴露给 Agent,
不允许 Agent 直接碰真实世界的底层接口。​


二、先设计一个通用的 Tool 抽象

2.1 基础接口定义

先用一个极简抽象,把"工具"统一成一个接口:

python 复制代码
from abc import ABC, abstractmethod
from typing import Any, Dict

class Tool(ABC):
    """所有 Agent 工具的抽象基类"""

    @abstractmethod
    def name(self) -> str:
        """工具唯一标识,用于在规划和调用时引用"""
        raise NotImplementedError

    @abstractmethod
    def description(self) -> str:
        """给 LLM/Agent 看的工具说明,写清楚能干嘛、不能干嘛"""
        raise NotImplementedError

    @abstractmethod
    def input_schema(self) -> Dict[str, Any]:
        """输入参数约束(字段名、类型、是否必填、取值范围等)"""
        raise NotImplementedError

    @abstractmethod
    def run(self, **kwargs) -> Any:
        """真正执行工具逻辑"""
        raise NotImplementedError

有了这个抽象之后:

  • 所有工具都有统一的"元信息"(name / description / 输入约束);
  • 可以很方便生成「给大模型看的工具列表」;
  • 也方便你在执行前做参数校验、权限控制和审计。

2.2 工具注册中心(Registry)

再实现一个 Registry,把所有 Tool 管理起来:

python 复制代码
from typing import Dict, List, Optional

class ToolRegistry:
    def __init__(self):
        self._tools: Dict[str, Tool] = {}

    def register(self, tool: Tool):
        if tool.name() in self._tools:
            raise ValueError(f"Tool {tool.name()} already registered")
        self._tools[tool.name()] = tool

    def get(self, name: str) -> Optional[Tool]:
        return self._tools.get(name)

    def list_tools(self) -> List[Tool]:
        return list(self._tools.values())

    def list_tool_specs_for_llm(self) -> List[Dict[str, Any]]:
        """给 LLM 用的工具描述(用于自动选工具)"""
        specs = []
        for t in self._tools.values():
            specs.append({
                "name": t.name(),
                "description": t.description(),
                "input_schema": t.input_schema()
            })
        return specs

之后 Agent 的"执行层 / 规划层"就不要直接依赖某个具体工具,而是依赖这个 ToolRegistry


三、给 Agent 装上几类典型技能

下面我用一个"订单排障 Agent"的场景举例,给它装三类常见工具:

  1. 查询类(只读)
  2. 分析类(本地逻辑 / 子模型调用)
  3. 行动类(有副作用的操作)

3.1 查询类工具:GetOrderDetail

python 复制代码
class GetOrderDetailTool(Tool):
    def __init__(self, order_client):
        self.order_client = order_client

    def name(self) -> str:
        return "get_order_detail"

    def description(self) -> str:
        return "根据 order_id 查询订单基础信息,仅支持只读操作。"

    def input_schema(self) -> Dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "order_id": {"type": "string", "description": "订单ID"}
            },
            "required": ["order_id"]
        }

    def run(self, **kwargs) -> Dict[str, Any]:
        order_id = kwargs["order_id"]
        # 这里调用真实业务 API
        data = self.order_client.get(order_id)
        return {
            "order_id": data.id,
            "status": data.status,
            "amount": data.amount,
            "created_at": data.created_at.isoformat()
        }

3.2 分析类工具:AnalyzePaymentError

python 复制代码
class AnalyzePaymentErrorTool(Tool):
    def name(self) -> str:
        return "analyze_payment_error"

    def description(self) -> str:
        return "根据支付错误码和日志,给出简要的错误原因分类。"

    def input_schema(self) -> Dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "error_code": {"type": "string"},
                "logs": {"type": "array", "items": {"type": "string"}}
            },
            "required": ["error_code"]
        }

    def run(self, **kwargs) -> Dict[str, Any]:
        code = kwargs["error_code"]
        # 非LLM的业务逻辑示例,也可以在内部再次调用一个小模型
        if code in ("CONN_TIMEOUT", "BANK_TIMEOUT"):
            category = "NETWORK_OR_UPSTREAM"
        elif code.startswith("CARD_"):
            category = "USER_SIDE"
        else:
            category = "UNKNOWN"

        return {
            "category": category,
            "explanation": f"支付错误可能属于 {category} 类问题"
        }

3.3 行动类工具:CreateTicket(高危需限权)

python 复制代码
class CreateTicketTool(Tool):
    def __init__(self, ticket_client):
        self.ticket_client = ticket_client

    def name(self) -> str:
        return "create_ticket"

    def description(self) -> str:
        return "创建一条运维/客服工单,用于后续人工跟进。(需权限检查)"

    def input_schema(self) -> Dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "title": {"type": "string"},
                "content": {"type": "string"},
                "severity": {"type": "string", "enum": ["low", "medium", "high", "critical"]}
            },
            "required": ["title", "content"]
        }

    def run(self, **kwargs) -> Dict[str, Any]:
        # 注意:这里在调用前应该经过上层的权限控制
        ticket_id = self.ticket_client.create(
            title=kwargs["title"],
            content=kwargs["content"],
            severity=kwargs.get("severity", "medium")
        )
        return {"ticket_id": ticket_id, "status": "created"}

注册工具:

python 复制代码
registry = ToolRegistry()
registry.register(GetOrderDetailTool(order_client))
registry.register(AnalyzePaymentErrorTool())
registry.register(CreateTicketTool(ticket_client))

四、让模型"自动选工具":从自然语言到 Tool 调用

4.1 给 LLM 的工具列表提示(Tool Spec)

有了 ToolRegistry,你就可以在 Prompt 里给 LLM 一个结构化的"工具说明":

python 复制代码
def build_tool_prompt(tools_for_llm: List[Dict[str, Any]]) -> str:
    lines = ["你可以调用以下工具来完成任务:"]
    for spec in tools_for_llm:
        lines.append(f"- {spec['name']}: {spec['description']}")
        lines.append(f"  参数: {json.dumps(spec['input_schema'], ensure_ascii=False)}")
    lines.append("""
请根据当前任务,选一个最合适的工具,并给出调用参数。
只输出JSON,不要任何解释,格式如下:
{
  "tool": "<工具名>",
  "args": { ... }
}
""")
    return "\n".join(lines)

在 Agent 运行中,你可以这么用:

python 复制代码
tool_prompt = build_tool_prompt(registry.list_tool_specs_for_llm())
llm_input = f"""
【任务目标】
{goal}

【当前上下文】
{json.dumps(context, ensure_ascii=False)}

{tool_prompt}
"""
tool_call_json = llm(llm_input)  # 调用大模型
tool_call = json.loads(tool_call_json)
tool_name = tool_call["tool"]
args = tool_call["args"]

然后交给执行器去跑。

4.2 参数安全校验

在真正调用前,一定要做参数校验,防止模型乱填:

python 复制代码
from jsonschema import validate, ValidationError

def safe_execute_tool(registry: ToolRegistry, tool_name: str, raw_args: Dict[str, Any]) -> Dict[str, Any]:
    tool = registry.get(tool_name)
    if not tool:
        return {"error": f"unknown_tool: {tool_name}"}
    
    schema = tool.input_schema()
    try:
        validate(instance=raw_args, schema=schema)
    except ValidationError as e:
        return {"error": f"invalid_args: {e.message}"}
    
    # 这里再接入权限检查
    try:
        result = tool.run(**raw_args)
        return {"result": result}
    except Exception as e:
        return {"error": f"tool_execution_failed: {str(e)}"}

五、安全边界:不要让 Agent 变成"乱来"的超级管理员

工具层是最容易出事的地方,因此必须非常清晰地设计安全边界。

可以从三层来做防护:

5.1 工具白名单 + 分级

  • 每个 Agent 实例都有一份"可用工具白名单";
  • 对工具按风险分级:
    • 只读:查询订单、查日志、查配置;
    • 低风险写:创建工单、发通知、写审计日志;
    • 高风险写:重启、扩容、改配置、删数据。

Tool 抽象上可以加一个 level

python 复制代码
class Tool(ABC):
    @abstractmethod
    def level(self) -> str:
        """
        工具风险级别:
        - read_only
        - low_risk
        - high_risk
        """
        ...

然后在执行器里按 level 决定是否允许自动执行、是否需要人工确认。

5.2 上下文约束 + 任务约束

  • 在任务描述中加入 constraints
    • 如 "只读操作"、 "不允许重启生产环境服务";
  • 在 Tool 层读取这些约束,动态决定是否允许执行。

执行前加一层检查:

python 复制代码
def check_constraints(task_constraints: List[str], tool: Tool) -> bool:
    if "只读操作" in task_constraints and tool.level() != "read_only":
        return False
    # 可以继续加更多业务规则
    return True

5.3 审计与回放

  • 所有工具调用都必须带上:
    • 谁发起(用户 / 系统);
    • 哪个 Agent;
    • 何时;
    • 调了哪个 Tool、传了什么参数、结果如何。
  • 这些记录既是安全审计,也是后续调试 Agent 行为的关键数据。

六、把工具层嵌回你的 Agent 架构里

结合你前一篇的架构(意图 → 规划 → 工具执行 → 状态 → 评估),现在我们可以把工具层正式放进去:

  1. 意图解析 :得到 task_type / goal / params / constraints
  2. 规划 :基于 task_type 生成一个或多个 PlanStep(每个包含 actionargs);
  3. 执行
    • 使用 ToolRegistry 根据 action 查找 Tool;
    • input_schema 校验 args
    • 检查 constraints + level 决定是否允许调用;
    • 调用 tool.run,把结果写回 AgentState
  4. 评估:看这一步的结果是否足以完成目标,如果不够,再走下一步规划/执行。

在代码结构上,你可以把工具层单独一个 package:

python 复制代码
agent_framework/
  core/
    intent_parser.py
    planner.py
    executor.py      # 只依赖 ToolRegistry
    state.py
    evaluator.py
  tools/
    base.py          # Tool 抽象
    registry.py
    order_tools.py
    ticket_tools.py
    ...

七、落地建议:先从"只读工具"开始

如果你现在还没在生产上跑 Agent,或者还在 PoC 阶段,建议按这个顺序来:

  1. 只接只读工具

    • get_xxx / list_xxx / describe_xxx
    • 不做任何写操作,先绕开安全风险。
  2. 严格做参数校验

    • 给每个工具写好 input_schema
    • 执行前用 jsonschema 校验。
  3. 慢慢放开低风险写工具

    • create_ticket / send_notification / write_audit_log;
    • 一开始可以要求"需人工点击确认"才执行。
  4. 最后再接高风险写工具

    • scale_service / restart_pod / change_config 等;
    • 必须配合权限系统、变更流程和回滚策略。

八、小结

「给 Agent 安装技能」这一块拆开讲清楚了:

  • 用统一的 Tool 抽象 + ToolRegistry 管理所有工具;
  • 把业务 API、分析逻辑、自愈动作都包装成受控工具;
  • 通过工具列表 Prompt + LLM,让 Agent 能自动选工具;
  • 用输入 schema + 约束检查 + 风险分级,给工具层筑起安全边界;
  • 把工具层稳稳嵌入你上一篇的 5 层 Agent 架构中。
相关推荐
量化炼金 (CodeAlchemy)1 小时前
【交易策略】低通滤波器策略:在小时图上捕捉中期动量
大数据·人工智能·机器学习·区块链
智算菩萨1 小时前
上下文学习的贝叶斯推断视角:隐式梯度下降还是隐式贝叶斯?
人工智能·算法
带娃的IT创业者1 小时前
解密OpenClaw系列04-OpenClaw技术架构
macos·架构·cocoa·agent·ai agent·openclaw
看-是灰机1 小时前
openclaw
人工智能
ljxp12345682 小时前
高效删除链表重复节点
python
52Hz1182 小时前
力扣207.课程表、208.实现Trie(前缀树)
python·leetcode
骇城迷影2 小时前
从零复现GPT-2 124M
人工智能·pytorch·python·gpt·深度学习
黑巧克力可减脂2 小时前
商鞅变法与代码重构:AI正在如何重写软件工程的“耕战律令”
人工智能·重构·软件工程
kronos.荒2 小时前
滑动窗口:寻找字符串中的字母异位词
开发语言·python