chore: initialize sandbox and overwrite remote content
Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled

This commit is contained in:
codex-bot
2026-03-02 22:32:27 +08:00
commit a64378956a
584 changed files with 93604 additions and 0 deletions

View File

@@ -0,0 +1,457 @@
# -*- coding: utf-8 -*-
"""
.. _tool:
工具
=========================
为了确保准确可靠的工具解析AgentScope 全面支持工具 API 的使用,具有以下特性:
- 支持从文档字符串 **自动** 解析工具函数
- 支持 **同步和异步** 工具函数
- 支持 **流式** 工具响应(同步或异步生成器)
- 支持对工具 JSON Schema 的 **动态扩展**
- 支持用户实时 **中断** 工具的执行
- 支持智能体的 **自主工具管理**
所有上述功能都由 AgentScope 中的 ``Toolkit`` 类实现,该类负责管理工具函数及其执行。
.. tip:: MCP模型上下文协议的支持请参考 :ref:`mcp` 部分。
"""
import asyncio
import inspect
import json
from typing import Any, AsyncGenerator
from pydantic import BaseModel, Field
import agentscope
from agentscope.message import TextBlock, ToolUseBlock
from agentscope.tool import ToolResponse, Toolkit, execute_python_code
# %%
# 工具函数
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 在 AgentScope 中,工具函数是一个 Python 的可调用对象,它
#
# - 返回一个 ``ToolResponse`` 对象或产生 ``ToolResponse`` 对象的生成器(可以是异步或同步)
# - 具有描述工具功能和参数的文档字符串
#
# 工具函数的模板如下:
def tool_function(a: int, b: str) -> ToolResponse:
"""{函数描述}
Args:
a (int):
{第一个参数的描述}
b (str):
{第二个参数的描述}
"""
# %%
# .. tip:: 实例方法和类方法也可以用作工具函数,``Toolkit`` 中将自动忽略 ``self`` 和 ``cls`` 参数。
#
# AgentScope 在 ``agentscope.tool`` 模块下提供了几个内置工具函数,如 ``execute_python_code``、``execute_shell_command`` 和文本文件读写函数。
#
print("内置工具函数:")
for _ in agentscope.tool.__all__:
if _ not in ["Toolkit", "ToolResponse"]:
print(_)
# %%
# 工具模块Toolkit
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ``Toolkit`` 类设计用于管理工具函数,从文档字符串中提取它们的 JSON Schema并为工具执行提供统一接口。
#
# 基本用法
# ------------------------------
# ``Toolkit`` 类的基本功能是注册工具函数并执行它们。
#
# 准备一个自定义工具函数
async def my_search(query: str, api_key: str) -> ToolResponse:
"""一个简单的示例工具函数。
Args:
query (str):
搜索查询。
api_key (str):
用于身份验证的 API 密钥。
"""
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"正在使用 API 密钥 '{api_key}' 搜索 '{query}'",
),
],
)
# 在工具模块中注册工具函数
toolkit = Toolkit()
toolkit.register_tool_function(my_search)
# %%
# 注册工具函数后,可以通过调用 ``get_json_schemas`` 方法获取其 JSON Schema。
#
print("工具 JSON Schemas")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# ``Toolkit`` 还允许开发者为工具函数预设参数,这对于 API 密钥或其他敏感信息特别有用。
#
# 先清空工具模块
toolkit.clear()
# 使用预设关键字参数注册工具函数
toolkit.register_tool_function(my_search, preset_kwargs={"api_key": "xxx"})
print("带预设参数的工具 JSON Schemas")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# 预设参数后,该参数将从 JSON schema 中被移除,并在工具调用时自动传递给该工具函数。
#
# 在 ``Toolkit`` 中,``call_tool_function`` 方法以 ``ToolUseBlock`` 作为输入执行指定的工具函数,统一返回一个 **异步生成器**,该生成器产生 ``ToolResponse`` 对象。
#
# .. note:: AgentScope 中,流式返回的工具函数应该是 **“累积的”**,即当前块的内容应包含之前所有块的内容。
#
async def example_tool_execution() -> None:
"""工具调用执行示例。"""
res = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="123",
name="my_search",
input={"query": "AgentScope"},
),
)
# 非流式返回的工具函数只有一个 ToolResponse 返回
print("工具响应:")
async for tool_response in res:
print(tool_response)
asyncio.run(example_tool_execution())
# %%
# 动态扩展 JSON Schema
# --------------------------------------
#
# Toolkit 允许通过调用 ``set_extended_model`` 方法动态扩展工具函数的 JSON schemas。
# 这种功能允许开发者在不修改工具函数原始定义的情况下,向工具函数添加更多参数。
#
# .. tip:: 相关场景包括动态 :ref:`structured-output` 和 CoT思维链推理
#
# .. note:: 要扩展的工具函数应该接受可变关键字参数(``**kwargs``),以便附加字段可以传递给它。
#
# 以 CoT 推理为例,我们可以用 ``thinking`` 字段扩展所有工具函数,允许智能体总结当前状态然后决定下一步做什么。
#
# 示例工具函数
def tool_function(**kwargs: Any) -> ToolResponse:
"""一个工具函数"""
return ToolResponse(
content=[
TextBlock(
type="text",
text=f"接收到的参数:{kwargs}",
),
],
)
# 添加一个思考字段,以便智能体在给出其他参数之前可以思考。
class ThinkingModel(BaseModel):
"""用于附加字段的 Pydantic 模型。"""
thinking: str = Field(
description="总结当前状态并决定下一步做什么。",
)
# 注册
toolkit.set_extended_model("my_search", ThinkingModel)
print("扩展后的 JSON Schema")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# 中断工具执行
# ------------------------------
# ``Toolkit`` 类支持 **异步工具函数** 的 **执行中断**,并提供 **面向智能体的后处理机制**。
# 这种中断基于 asyncio 取消机制实现,其后处理过程根据工具函数的返回类型而有所不同。
#
# .. note:: 对于同步(工具)函数,它们的执行无法通过 asyncio 取消来中断。因此其中断在智能体内而不是工具模块内处理。
# 有关更多信息,请参考 :ref:`agent` 部分。
#
# 具体来说,如果工具函数返回 ``ToolResponse`` 对象,将产生一个带有中断消息的 ``ToolResponse`` 对象。
# 这样智能体可以观察到这一中断并相应地处理它。
# 此外,该 ``ToolResponse`` 对象中的 ``is_interrupted`` 将设置为 ``True``,外部调用者可以决定是否将 ``CancelledError`` 异常抛出到外层。
#
# 可以被中断的异步工具函数示例如下:
#
async def non_streaming_function() -> ToolResponse:
"""一个可以被中断的非流式工具函数。"""
await asyncio.sleep(1) # 模拟长时间运行的任务
# 为演示目的模拟中断
raise asyncio.CancelledError()
# 由于取消,以下代码不会被执行
return ToolResponse(
content=[
TextBlock(
type="text",
text="运行成功!",
),
],
)
async def example_tool_interruption() -> None:
"""工具中断示例。"""
toolkit = Toolkit()
toolkit.register_tool_function(non_streaming_function)
res = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="123",
name="non_streaming_function",
input={},
),
)
async for tool_response in res:
print("工具响应:")
print(tool_response)
print("中断标志:")
print(tool_response.is_interrupted)
asyncio.run(example_tool_interruption())
# %%
# 对于流式工具函数,``Toolkit`` 将把中断消息附加到中断发生时的 ``ToolResponse`` 上。
# 通过这种方式,智能体可以观察到工具在中断前返回的内容。
#
# 中断流式工具函数的示例如下:
#
async def streaming_function() -> AsyncGenerator[ToolResponse, None]:
"""一个可以被中断的流式工具函数。"""
# 模拟一块响应
yield ToolResponse(
content=[
TextBlock(
type="text",
text="1234",
),
],
stream=True,
)
# 模拟中断
raise asyncio.CancelledError()
# 由于取消,以下代码不会被执行
yield ToolResponse(
content=[
TextBlock(
type="text",
text="123456789",
),
],
)
async def example_streaming_tool_interruption() -> None:
"""流式工具中断示例。"""
toolkit = Toolkit()
toolkit.register_tool_function(streaming_function)
res = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="xxx",
name="streaming_function",
input={},
),
)
i = 0
async for tool_response in res:
print(f"{i}")
print(tool_response)
print("中断标志:", tool_response.is_interrupted, "\n")
i += 1
asyncio.run(example_streaming_tool_interruption())
# %%
# 自动工具管理
# -------------------------------------
# .. image:: https://img.alicdn.com/imgextra/i3/O1CN013cvRpO27MfesMsTeh_!!6000000007783-2-tps-840-521.png
# :width: 100%
# :align: center
# :alt: 自动工具管理
#
#
# ``Toolkit`` 类通过引入 **工具组** Group 的概念,以及名为 ``reset_equipped_tools`` 的 **元工具函数** Meta Tool 来支持 **自动工具管理** 。
#
# 工具组是一组相关工具函数的集合,例如浏览器使用工具、地图服务工具等,它们将被一起管理。工具组有激活和非激活两种状态,
# 只有工具组被激活,其中的工具函数才对智能体可见,即可以通过 ``toolkit.get_json_schemas()`` 方法访问。
#
# 注意有一个名为 ``basic`` 的特殊组,它始终处于激活状态,注册工具时如果未指定组名,则工具函数将默认添加到此组。
#
# .. tip:: ``basic`` 组确保开发者不需要“组管理”的功能时,工具的基本使用不会受到影响。
#
# 现在我们尝试创建一个名为 ``browser_use`` 的工具组,其中包含一些网页浏览工具。
#
# 我们创建一些浏览器操作相关的工具
def navigate(url: str) -> ToolResponse:
"""导航到网页。
Args:
url (str):
要导航到的网页的 URL。
"""
pass
def click_element(element_id: str) -> ToolResponse:
"""点击网页上的元素。
Args:
element_id (str):
要点击的元素的 ID。
"""
pass
toolkit = Toolkit()
# 创建一个名为 browser_use 的工具组
toolkit.create_tool_group(
group_name="browser_use",
description="用于网页浏览的工具函数。",
active=False,
# 使用这些工具时的注意事项
notes="""1. 使用 ``navigate`` 打开网页。
2. 当需要用户身份验证时,请向用户询问凭据
3. ...""",
)
toolkit.register_tool_function(navigate, group_name="browser_use")
toolkit.register_tool_function(click_element, group_name="browser_use")
# 我们也可以注册一些基本工具
toolkit.register_tool_function(execute_python_code)
# %%
# 此时 ``browser_use`` 未被激活,如果我们检查工具 JSON schema只能看到 ``execute_python_code`` 工具:
print("此时对智能体可见的工具函数 JSON Schemas")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# 使用 ``update_tool_groups`` 方法激活或停用工具组:
toolkit.update_tool_groups(group_names=["browser_use"], active=True)
print("激活后对智能体可见的工具函数 JSON Schemas")
print(json.dumps(toolkit.get_json_schemas(), indent=4, ensure_ascii=False))
# %%
# 此外,``Toolkit`` 提供了一个名为 ``reset_equipped_tools`` 的元工具函数,它会将所有组名(除了 "basic")作为一个 bool 型的参数,
# 让智能体调用该工具来决定要激活哪些工具组:
#
# .. note:: 在 ``ReActAgent`` 类的实现中,只需要在构造函数中将 ``enable_meta_tool`` 设置为 ``True`` 即可启用元工具函数。
#
# 注册元工具函数
toolkit.register_tool_function(toolkit.reset_equipped_tools)
reset_equipped = next(
tool
for tool in toolkit.get_json_schemas()
if tool["function"]["name"] == "reset_equipped_tools"
)
print("``reset_equipped_tools`` 函数的 JSON schema")
print(
json.dumps(
reset_equipped,
indent=4,
ensure_ascii=False,
),
)
# %%
# 当智能体调用 ``reset_equipped_tools`` 时,对应工具组将被激活,同时返回的结果中将包含工具的使用注意事项。
#
async def mock_agent_reset_tools() -> None:
"""模拟智能体调用 reset_equipped_tools 函数。"""
res = await toolkit.call_tool_function(
ToolUseBlock(
type="tool_use",
id="456",
name="reset_equipped_tools",
input={
"browser_use": True, # 激活浏览器使用工具组
},
),
)
async for tool_response in res:
print("工具响应中的文字返回:")
print(tool_response.content[0]["text"])
asyncio.run(mock_agent_reset_tools())
# %%
# 此外,``Toolkit`` 还通过 ``get_activated_notes`` 函数提供已经被激活了的工具组的 notes开发者也可以将其组装到智能体的系统提示中从而达到动态管理工具的作用。
#
# .. tip:: 自动工具管理功能已在 ``ReActAgent`` 类中实现,有关更多详细信息,请参考 :ref:`agent` 部分。
#
# 再创建一个工具组
toolkit.create_tool_group(
group_name="map_service",
description="谷歌地图服务工具。",
active=True,
notes="""1. 使用 ``get_location`` 获取地点的位置。
2. ...""",
)
print("激活工具组的汇总注意事项:")
print(toolkit.get_activated_notes())
# %%
# 进一步阅读
# ---------------------
# - :ref:`agent`
# - :ref:`state`
# - :ref:`mcp`
#