跳转到主要内容

概述

工具是智能体与外部世界交互的途径 —— 执行 shell 命令、读写文件、调用 API。每个工具通过 JSON Schema 暴露给 LLM,智能体通过统一的接口完成调用。 AgentScope 包含以下三个工具相关的概念:
  • 工具:继承自 ToolBase 的子类,包括 AgentScope 内置工具,以及将 MCP 和 Python 函数封装成 ToolBase 子类的 FunctionTool / MCPTool 包装器。
  • Toolkit:工具管理模块,负责注册工具、MCP 与 skill,向 LLM 暴露它们的信息并最终执行工具调用。
  • 工具组(Tool group):一组工具 / MCP / skill 集合,可以作为整体被智能体激活或停用。智能体在运行时通过内置元工具(Meta tool)控制工具组。
from agentscope.tool import Toolkit, Bash, Read, Write, Edit

toolkit = Toolkit(
    tools=[Bash(), Read(), Write(), Edit()],
)
只传 tools 时,这些工具都进入特殊的 "basic" 组 —— 该组始终激活。追加 mcpsskills_or_loaders 或额外的 tool_groups 即可拓展智能体的能力 —— 见下文各节。

Python 函数工具

Python 函数工具是任意继承 ToolBase 基类的对象。AgentScope 提供了一组内置工具覆盖常见操作,并对外暴露同一接口供开发者自定义。

ToolBase 接口

ToolBase 是所有工具的抽象基类。下表列出其属性与方法。 工具的属性:
属性类型说明
namestr暴露给智能体的工具名称
descriptionstr面向智能体的功能描述
input_schemadict定义参数的 JSON Schema
is_concurrency_safebool是否可并发调用
is_read_onlybool是否只读、不产生副作用
is_external_toolboolTrue 时执行委派给外部执行(见 定义外部执行工具
is_state_injectedboolTrue 时通过 _agent_state 参数注入智能体状态数据
is_mcpbool是否来自 MCP 服务
mcp_namestr | Noneis_mcpTrue 时所属 MCP 服务名
ToolBase 提供的核心方法:
方法必需说明
check_permissions(tool_input, context)执行前的运行时权限检查;返回 PermissionDecision
check_read_only(tool_input)可选单次调用的只读判定;返回 bool。默认返回 self.is_read_only。当只读性取决于输入时覆写(例如 Bashls 是只读,rm 不是)。权限引擎在决定 EXPLORE / ACCEPT_EDITS 是否自动放行时调用。
match_rule(rule_content, tool_input)可选权限系统中的自定义规则匹配;返回 bool
generate_suggestions(tool_input)可选基于本次工具调用生成建议规则;返回 list[PermissionRule]
call(**kwargs)是*工具的执行逻辑 —— 子类 override 点。返回 ToolChunkAsyncGenerator[ToolChunk, None]。外部执行工具(is_external_tool = True)不需要实现。
__call__(**kwargs)框架调度入口。依次运行中间件链(若有),再委托给 call()。不要 override。

使用内置工具

AgentScope 预置了一组覆盖常见智能体操作的工具,实例化后传入 Toolkit(tools=[...]) 即可:
工具说明只读
Bash执行 shell 命令
Read按行号读取文件内容
Write创建或覆写文件
Edit在文件中做精确字符串替换
Glob按 glob 模式查找文件
Grep基于 ripgrep 搜索文件内容
TaskCreate创建结构化任务以跟踪进度
TaskGet按 ID 获取任务详情
TaskList列出全部任务及其状态
TaskUpdate更新任务状态或元数据
元工具 reset_toolsSkill 查看工具只有在有额外的工具组存在,以及有 skill 时才会自动注册,开发者无需手动实例化。详见 自主管理工具Skill

Bash

Bash 工具执行 shell 命令并返回 stdout / stderr。它实现了所有可选接口方法,提供精细的权限控制。 check_permissions() 对命令字符串做分层安全分析:
  1. 注入风险检测 —— 标记 $(...)、反引号、进程替换等无法静态分析的动态结构 → ASK
  2. 只读命令检测 —— 自动放行安全命令(git statuslscatgrepdocker ps 等),包括所有子命令均只读的复合命令 → ALLOW
  3. 危险命令模式 —— 识别破坏性操作(如 chmod 777mkfs) → ASK
  4. Sed 约束检查 —— 阻止针对危险文件的就地 sed -i → ASK
  5. 危险路径保护 —— 检查命令是否操作敏感配置文件(.bashrc.ssh/.env) → ASK
  6. 危险删除检测 —— 捕获指向关键系统路径(/~/usr)的 rm / rmdir → ASK
  7. ACCEPT_EDITS 模式 —— 自动放行文件系统命令(mkdirtouchrmrmdirmvcpsed),且仅当所有目标路径都在某个配置的工作目录内时。任一目标路径越出工作集(例如 cp /etc/hosts /tmp/x)会落到 PASSTHROUGH 而不自动放行。
check_read_only() 在上述第 2 步只读检测器识别出的任何命令上返回 True,其余返回 False。权限引擎用它在 EXPLORE / ACCEPT_EDITS 决定自动放行,避免重复跑完整的安全分析。 match_rule() 使用基于前缀的通配匹配:
模式匹配不匹配
npm run:*npm run buildnpm run testnpm install
git commit:*git commit -m "fix"git push
rm:*rm file.txtrm -rf /tmp/xls
generate_suggestions() 抽取命令前缀(前两个 token)并给出前缀规则。例如 git commit -m "fix bug" 生成建议 git commit:* 构造函数支持向危险路径列表追加自定义条目:
from agentscope.tool import Bash

bash = Bash(
    additional_dangerous_files=[".secrets"],
    additional_dangerous_directories=[".credentials"],
)

文件工具

文件工具强制执行「先读后写」规则:WriteEdit 要求目标文件先经由 Read 读取过。这避免了盲目覆写,并保证智能体总是基于最新内容进行操作。
工具操作关键行为
Read读取文件内容返回带行号的内容;支持 offset / limit 处理大文件;结果缓存在 agent.state
Write创建或覆写文件文件已存在但未被读取过则失败
Edit替换文件中的精确字符串old_string 找不到或不唯一时失败(除非 replace_all=True);要求先读取
check_permissions() —— WriteEdit 共用同一权限逻辑:
  1. 危险路径保护 —— 操作敏感文件(.bashrc.env.ssh/)返回带 bypass_immune=True 的 ASK,allow 规则无法静默授权。BYPASS 模式下该 ASK 仍然被跳过(BYPASS 明确选择放弃 safety 提示),DONT_ASK 下被转为 DENY。完整契约见权限系统文档
  2. ACCEPT_EDITS 模式 —— 自动放行配置工作目录内的文件操作
  3. PASSTHROUGH —— 交给权限引擎做规则匹配
Read 是只读工具,始终返回 PASSTHROUGH(EXPLORE 与 ACCEPT_EDITS 模式下的自动放行由引擎通过 check_read_only 处理)。 match_rule() —— 三个工具都使用 fnmatchfile_path 参数做 glob 匹配:
模式匹配
src/**src/ 下任意文件
src/**/*.pysrc/ 下的 Python 文件
config.json精确匹配该文件
generate_suggestions() 提议覆盖父目录的 glob。例如编辑 /project/src/main.py 会生成建议 src/**

计划工具

计划工具让智能体能够显式地维护一份结构化的任务清单,智能体可以通过工具调用来创建、查询和更新任务。计划相关的数据会被存储在智能体实例的 agent.state.tasks_context 中,所有计划相关的工具通过操作这个共享的状态来实现任务的管理。同时,所有计划相关的工具在权限检查中被默认放行。 完整的任务生命周期、存储模型,以及如何以编程方式预置或自定义任务,请参见 计划模式

切换工具后端

AgentScope 中的 BashGrepGlobReadWriteEdit 工具支持后端切换,即将运行逻辑委派到不同的执行环境中,例如本地文件系统,Docker 容器,E2B 沙箱等。 通过指定 backend 参数即可切换后端,而 backend 实例可以通过 Workspace 实例获取,默认为本地环境。关于 Workspace 的更多信息,请参见 工作空间 章节。
from agentscope.workspace import DockerWorkspace

# 获取 Docker 沙箱的 backend
workspace = DockerWorkspace(...)
await workspace.initialize()

backend = workspace.get_backend()

# 配置工具
bash = Bash(backend=backend)

# 运行逻辑
...

await workspace.close()

自定义工具

通过继承 ToolBase 基类并实现对应的抽象接口即可创建自定义工具,同时通过设定相关的属性可以控制工具的权限审查和执行逻辑。
from agentscope.tool import ToolBase, ToolChunk
from agentscope.permission import (
    PermissionContext, PermissionDecision, PermissionBehavior,
)
from agentscope.message import TextBlock

class WebSearch(ToolBase):
    name = "WebSearch"
    description = "Search the web for information on a given query."
    input_schema = {
        "type": "object",
        "properties": {
            "query": {
                "type": "string",
                "description": "The search query.",
            },
        },
        "required": ["query"],
    }
    is_concurrency_safe = True
    is_read_only = True

    async def check_permissions(
        self, tool_input: dict, context: PermissionContext,
    ) -> PermissionDecision:
        return PermissionDecision(
            behavior=PermissionBehavior.ALLOW,
            message="Web search is read-only.",
        )

    async def call(self, query: str) -> ToolChunk:
        results = await do_search(query)
        return ToolChunk(content=[TextBlock(text=results)])
自定义工具时,有两个权限审查相关的逻辑需要注意:
  • check_read_only(tool_input):当某次调用是否是只读取决于输入时,需要重新该函数(例如 Bashls 是只读,rm 不是)。该函数默认返回 is_read_only 静态属性。权限引擎在判定 EXPLORE / ACCEPT_EDITS 是否自动放行时调用。
  • PermissionDecision(..., bypass_immune=True):在返回的 ASK 上设置,把它标记为 allow 规则无法静默的 safety check(例如 DeployTool 标记 prod-* 目标)。各 mode 下的具体处理见 safety check 契约

将函数包装为工具

当需要把一个 Python 函数暴露智能体时,一个轻量化的方法是用 FunctionTool 适配器包装。它会自动从 func.__name__ 取工具名、从 docstring 提取工具描述、从类型注解推导 input schema。
from agentscope.tool import FunctionTool, Toolkit

def get_weather(city: str, unit: str = "celsius") -> str:
    """Get the current weather for a city.

    Args:
        city: The city name to look up.
        unit: Temperature unit, either "celsius" or "fahrenheit".
    """
    return f"The weather in {city} is 22°{unit[0].upper()}"

toolkit = Toolkit(tools=[FunctionTool(get_weather)])
当自动推导的默认值不合适时,FunctionTool 支持显式覆盖:
参数类型说明
funcCallable待包装的 Python 函数
namestr | None覆盖工具名(默认取 func.__name__
descriptionstr | None覆盖描述(默认取 docstring)
is_concurrency_safebool是否可并发调用(默认 True
is_read_onlybool是否无副作用(默认 False
is_state_injectedbool是否通过 _agent_state 注入智能体状态数据(默认 False
被包装的函数默认走 ASK 权限行为 —— 用户必须为每次调用显式放行。需要自定义权限逻辑时,请直接继承 ToolBase

定义外部执行工具

外部执行工具把实际执行委派给智能体运行时之外 —— 通常是人工操作员或外部系统。智能体调用此类工具时会发出 RequireExternalExecutionEvent 事件并退出 reply / reply_stream 函数,直到结果通过 ExternalExecutionResultEvent 回传。 这种模式是 human-in-the-loop 工作流的基础 —— 某些动作需要人工确认或人工执行。 创建外部执行工具只需把 is_external_tool 设为 True,不必实现 call 函数:
from agentscope.tool import ToolBase
from agentscope.permission import (
    PermissionContext, PermissionDecision, PermissionBehavior,
)

class HumanApproval(ToolBase):
    name = "HumanApproval"
    description = "Request human approval for a sensitive operation."
    input_schema = {
        "type": "object",
        "properties": {
            "action": {"type": "string", "description": "The action requiring approval."},
            "reason": {"type": "string", "description": "Why this action needs approval."},
        },
        "required": ["action", "reason"],
    }
    is_concurrency_safe = True
    is_read_only = False
    is_external_tool = True

    async def check_permissions(
        self, tool_input: dict, context: PermissionContext,
    ) -> PermissionDecision:
        return PermissionDecision(
            behavior=PermissionBehavior.ALLOW,
            message="External tool dispatch is always allowed.",
        )

中间件

工具中间件(Tool middleware)把洋葱式钩子直接挂到某个工具实例上。每次调用该工具时,已注册的中间件都会按顺序触发,包裹执行流程。 这与智能体级中间件是独立的两套机制:智能体中间件中的 on_acting 包裹 ReAct 循环内整个工具调用逻辑,而 ToolMiddlewareBase 只在工具自身的 call() 执行链内触发。

自定义中间件

实现自定义中间件需要继承 ToolMiddlewareBase 并实现唯一的抽象异步生成器方法 on_tool_call
参数类型说明
toolToolBase正在被调用的工具实例
input_kwargsdict[str, Any]本次调用的输入参数。将(可能经修改的)参数传给 next_handler,可控制内层接收到的内容
next_handlerCallable[..., AsyncGenerator[ToolChunk, None]]通过 next_handler(**input_kwargs) 继续执行下一层。无论底层工具是否流式,始终返回 async generator

执行模型

  • 第一个注册的 middleware 是最外层 —— 其前置逻辑最先执行,后置逻辑最后执行。
  • next_handler(**input_kwargs) 始终返回 AsyncGenerator[ToolChunk, None]。流式与非流式工具统一处理,中间件无需区分。
  • 最内层调用工具自身的 call()
middlewares[0].on_tool_call
  └─ middlewares[1].on_tool_call
       └─ ... → tool.call()

挂载中间件

通过 middlewares 参数向工具构造函数传入中间件实例列表:
from agentscope.tool import Bash

bash = Bash(middlewares=[LoggingMiddleware(), MetricsMiddleware()])
# 执行顺序:LoggingMiddleware → MetricsMiddleware → Bash.call()

示例

一个在调用前后打印日志的中间件,以及一个在出错时自动重试的中间件:
from typing import AsyncGenerator, Any, Callable
from agentscope.tool import ToolMiddlewareBase, ToolBase, ToolChunk, Bash


class LoggingMiddleware(ToolMiddlewareBase):
    async def on_tool_call(
        self,
        tool: ToolBase,
        input_kwargs: dict[str, Any],
        next_handler: Callable[..., AsyncGenerator[ToolChunk, None]],
    ) -> AsyncGenerator[ToolChunk, None]:
        print(f"→ 调用 {tool.name},参数:{input_kwargs}")
        async for chunk in next_handler(**input_kwargs):
            yield chunk
        print(f"✓ {tool.name} 执行完毕")


class RetryMiddleware(ToolMiddlewareBase):
    def __init__(self, max_attempts: int = 3):
        self.max_attempts = max_attempts

    async def on_tool_call(
        self,
        tool: ToolBase,
        input_kwargs: dict[str, Any],
        next_handler: Callable[..., AsyncGenerator[ToolChunk, None]],
    ) -> AsyncGenerator[ToolChunk, None]:
        for attempt in range(1, self.max_attempts + 1):
            try:
                async for chunk in next_handler(**input_kwargs):
                    yield chunk
                return
            except Exception as e:
                if attempt == self.max_attempts:
                    raise
                print(f"第 {attempt} 次失败:{e},重试中……")


bash = Bash(middlewares=[LoggingMiddleware(), RetryMiddleware(max_attempts=3)])
工具中间件 vs. 智能体中间件 —— 对属于工具本身的横切关注点(日志、metrics、重试),使用 ToolMiddlewareBase。若需要访问更广泛的智能体上下文 —— 权限决策、tool-call 事件或所在的 ReAct 轮次 —— 请使用 MiddlewareBase.on_acting。完整的智能体级钩子参考见 Middleware

MCP

AgentScope 集成 Model Context Protocol (MCP),让智能体可以接入任意 MCP 兼容的工具提供方。框架自动处理协议协商、工具发现与结果转换。 支持两种连接方式:
  • 有状态连接(Stateful)(STDIO 或 HTTP):持久会话,需显式 connect() / close() 生命周期
  • 无状态连接(Stateless)(仅 HTTP):每次调用临时建会话,无需生命周期管理
为了避免冲突,MCP 工具的命名空间为 mcp__{server_name}__{tool_name};并且被标注 readOnlyHint 的 MCP 工具会被权限系统识别为只读(在 EXPLORE 与 ACCEPT_EDITS 模式下自动放行;DEFAULT 模式下若没有 allow 规则命中,仍然会 ASK)。

注册 MCP 客户端

通过 Toolkit(mcps=[...]) 接口可以注册多个 MCP 客户端,其中带有状态的 MCP 客户端必须在构造 toolkit 之前完成连接。
from agentscope.mcp import MCPClient, StdioMCPConfig
from agentscope.tool import Toolkit

client = MCPClient(
    name="filesystem",
    is_stateful=True,
    mcp_config=StdioMCPConfig(
        command="mcp-server-filesystem",
        args=["--root", "/my/project"],
    ),
)

await client.connect()

toolkit = Toolkit(mcps=[client])
如果希望只暴露 MCP 服务的部分工具,可以在 client 上配置 enable_toolsdisable_tools 参数:
client = MCPClient(
    name="search",
    is_stateful=False,
    mcp_config=HttpMCPConfig(url="https://api.search.com/mcp"),
    enable_tools=["web_search", "image_search"],
)
需要在 Toolkit 之外直接调用 MCP 工具时,调用 await client.list_tools() 拿到 MCPTool 实例数组后,即可像普通 ToolBase 一样使用。

Skill

Skill 是 Markdown 格式的指令集,它无需写代码即可拓展智能体能力。一般情况下每个 skill 是一个目录,固定包含一个带 frontmatter 元数据与详细指令的 SKILL.md 文件。 与工具不同,skill 不能被直接调用。智能体通过自动注册的 Skill 查看器读取 skill 指令,再用现有的工具按指令执行。

注册 Skill

通过 Toolkitskills_or_loaders 参数传入 skill 来源。每一项可以是目录路径字符串、Skill 对象,或 SkillLoaderBase 子类:
from agentscope.tool import Toolkit

toolkit = Toolkit(
    skills_or_loaders=["/path/to/skills"],
)

Skill 的工作方式

Toolkit 在含 skill 时,注册与查看分两阶段进行。 初始化阶段:
  • Toolkit 扫描所有注册的 skill 来源,收集每个 skill 的名称、描述与目录。
  • 自动注册内置的 Skill 查看器。
  • 组装一段系统提示片段,列出可用 skill(仅名称与描述),并指示智能体通过 Skill 查看器读取完整内容。
运行时阶段:
  • 智能体按名字选定一个 skill,调用 Skill 查看器。
  • 查看器读取对应 SKILL.md,返回完整 Markdown。
  • 智能体用已装备的工具按这些指令执行。
Skill 不是工具 —— 智能体不能直接调用 skill。它必须先用 Skill 查看器读取指令,再用其他工具按描述的步骤执行。

自主管理工具

AgentScope 内置元工具(Meta tool)reset_tools)让智能体在运行时自主管理哪些工具组处于激活状态,从而保持上下文聚焦 —— 只有与当前任务相关的工具才会暴露给模型。

定义工具组

ToolGroup 是带有名称和描述的工具 / MCP / skill 集合,通过 Toolkit(tool_groups=[...]) 接口进行设置。其中保留名 "basic" 由构造函数顶层的 toolsmcpsskills_or_loaders 自动构成,且始终处于激活状态。
from agentscope.tool import Toolkit, ToolGroup, Bash, Read, Write, Edit

toolkit = Toolkit(
    tools=[Bash(), Read(), Write(), Edit()],
    tool_groups=[
        ToolGroup(
            name="database",
            description="Tools for database operations.",
            instructions="Always wrap mutations in a transaction.",
            tools=[db_query_tool, db_migrate_tool],
        ),
        ToolGroup(
            name="deployment",
            description="Tools for deploying services.",
            instructions="Confirm the target environment before deploying.",
            tools=[deploy_tool, rollback_tool],
        ),
    ],
)
ToolGroup 接收与 toolkit 相同的 toolsmcpsskills_or_loaders 参数,再加上一个用于向智能体描述本工具组的 description 字段,以及工具组激活时返回给智能体的可选 instructions 参数。

使用元工具

只要存在至少一个非 basic 的工具组,Toolkit 就会自动注册元工具 reset_tools。除默认组外,每个工具组都对应 reset_tools 中的一个布尔型参数,代表是否激活该工具组。 运行时行为:
  • "basic" 组中的工具始终暴露,元工具不会影响它们。
  • 每次调用 reset_tools 都会整体覆盖激活集合 —— 任何未显式置为 True 的非 basic 组都会被停用。
  • 被激活的工具组,其 instructions(若有)会被拼接进元工具的返回值,告诉智能体如何正确使用该组。
  • 未被激活的工具以及 skill 不会出现在 LLM 调用的输入中。
元工具的输入表示所有工具组的最终状态。任何未显式置为 True 的工具组都会被停用,无论之前的状态如何。

延伸阅读

智能体

智能体如何在 ReAct 循环中编排工具调用

权限系统

精细控制哪个工具可以执行、何时执行

中间件

拦截智能体生命周期钩子 —— reply、reasoning、model 调用等

人机协同

外部执行工具与人工审批工作流