Skip to content

Tools 工具

Tool 对 LLM 的作用,可以用一句话概括:它让 LLM 从一个“只能盲猜的聊天机器人”,变成了“拥有手和眼的实干家”。

在本框架中,每个Tool实现类的run()方法都要求返回Message类型的数据

ToolRegistry

ToolRegistry 是一个工具注册表,用于管理和调用工具(Tool)。它在整个 Agent 系统中扮演着"工具调度中心"的角色。以下是其核心功能模块:


1. 工具注册管理

ToolRegistry 维护了两套工具字典:

字典说明
_tools普通工具字典,对 LLM 可见(通过 get_openai_tools() 暴露给 OpenAI API)
_defer_tools延迟工具字典,不对外暴露,专门存储那些需要 Agent 发现后才能调用的工具

两种工具的主要区别在于:get_openai_tools()在没有任何参数情况下只会返回普通工具列表对应的List[Dict[str, Any]]数据以供LLM调用,而延迟工具列表对应的数据不能通过get_openai_tools()返回。简单说,就是普通工具列表对外暴露,延迟工具列表不对外暴露

2. 审批机制

ToolRegistry 在构造函数中接受一个可选的 ApprovalTool(审批工具)实例。当设置了审批工具后,每次执行工具前都会先经过用户审批,审批不通过则返回拒绝消息,不会真正执行工具。这为敏感操作提供了安全兜底。

框架提供了默认的ApprovalTool实现类,如果你想使用默认的ApprovalTool实现类,在创建ToolRegistry实例时传入DefaultApprovalTool()

python
tool_registry = ToolRegistry(DefaultApprovalTool(require_approval_tools=[TerminalTool],
                                                max_attempts=5,
                                                auto_approve_if_no_rules=True))

你也可以实现ApprovalTool类,并传入给ToolRegistry实例

默认审批实现思路博客:模板方法模式实战:重构Agent工具审批,告别重复代码 - 知乎

如何注册工具

推荐使用register_tools()注册工具

python
from violet_agents import ToolRegistry
from violet_agents import SkillsTool, TerminalTool

tool_registry = ToolRegistry()
tool_registry.register_tools(SkillsTool(), TerminalTool())

当然,你也可以使用register_tools(... , is_defer=False)来注册延迟工具。延迟工具目前需要配合内置的SearchToolsTool使用

python
from violet_agents import ToolRegistry
from violet_agents import SkillsTool, TerminalTool

tool_registry = ToolRegistry()
tool_registry.register_tools(SearchToolsTool(get_deferTools_callback=tool_registry.get_defer_tools, search_strategy="subAgent"))
tool_registry.register_tools(SkillsTool(), TerminalTool(), is_defer=True)

获取工具列表

若你需要获取工具列表,可以使用get_openai_tools()方法,返回类型为List[Dict[str, Any]]

python
tools = self.tool_registry.get_openai_tools()

内置Tool

本框架提供了常用的内置Tool以供调用

SearchToolsTool

一个专门用于搜索工具的工具,目前支持基于子Agent的搜索基于keyword的搜索两种策略。

SearchToolsTool可接收的参数为

参数类型是否必传说明
actionstring用于指定当前的操作类型,支持两种操作:search表示搜索相关工具,get表示获取具体工具信息。
querystring搜索查询字符串,仅在action为search时使用
tool_namestring要获取的工具名称,仅在action为get时使用

action == search时,该工具基于SearchToolsTool实例的get_deferTools_callback搜索工具。搜索成功将会返回Message类型的数据。

action == get时,该工具会基于Agent传入的tool_nameSearchToolsTool实例的get_deferTools_callback中进行搜索,搜索成功则会返回Message类型的数据,message.content即为工具参数

WARNING

action == get并搜索成功时,仅返回包含工具参数的Message数据,若想让Agent实现懒加载工具可以结合钩子函数与ToolRegistry,具体实现可以参考ReactAgent

实现思路博客:Tool 太多怎么办?基于渐进式披露实现 Agent Tool Search - 知乎

使用示例:

python
from violet_agents import ReactAgent
from violet_agents import VioletAgentsLLM
from violet_agents import ToolRegistry
from violet_agents import SkillsTool, TerminalTool, SearchToolsTool
from violet_agents import DefaultApprovalTool

tool_registry = ToolRegistry()
tool_registry.register_tools(SkillsTool(), SearchToolsTool(get_deferTools_callback=tool_registry.get_defer_tools, search_strategy="subAgent"))
tool_registry.register_tool(TerminalTool(), is_defer=True)
agent = ReactAgent(
    name="VioletAgent",
    llm=VioletAgentsLLM(provider="deepseek", model="deepseek-v4-flash"),
    tool_registry=tool_registry
)
response = agent.run("看看我当天文件夹有什么文件?")

TerminalTool

TerminalTool 是一个 沙箱化的命令行执行工具,继承自 Tool 基类。它的核心作用是让Agent 能够在受控的安全环境中执行操作系统命令,并获取输出结果。

SkillsTool

SkillsTool 是一个技能发现与加载工具,用于在项目中管理和使用可复用的"技能"(Skills)。技能是以 SKILL.md 文件的形式组织的,每个技能包含 YAML 前置元数据(frontmatter)和 Markdown 正文内容。

目前该框架仅支持采用utf-8编码读取SKILL.md文件

核心功能

1. 技能发现(_discover_skills
  • 遍历多个技能路径(self.skill_paths),查找每个子目录下的 SKILL.md 文件
  • 解析 SKILL.md 中的 YAML 前置元数据(namedescriptiontriggers 等)
  • 返回所有已发现技能的字典,键为技能名称,值为 (文件路径, 元数据字典) 元组
2. 技能列表(action="list"
  • 返回当前所有可用技能的概览信息,包括名称、描述和触发条件
3. 技能加载(action="load"
  • 加载指定技能的完整内容(去除前置元数据后的正文部分),供 AI 模型使用
4. 系统提示注入(get_system_prompt_section
  • 生成一段技能列表文本,用于注入到系统提示(System Prompt)中,让模型知道有哪些可用技能

自定义Tool

如果你需要自定义Tool,无论是有状态Tool还是无状态Tool,你需要实现两个方法,分别是run() -> Messageget_parameters() -> ToolParameters

get_parameters()用于定义工具的输入参数结构,返回ToolParameters对象。该方法会告诉LLM如何传参

在触发工具调用时会执行run()方法。

自定义无状态Tool

如果你需要自定义无状态Tool,只需实现run()以及get_parameters()即可

python
from violet_agents import Tool, Message, ToolParameters, ToolProperty
from typing import Dict, Any

class WeatherTool(Tool):
    def __init__(self):
        super().__init__(
            name="get_weather",
            description="获取指定城市的天气信息"
        )
    
    def run(self, parameters: Dict[str, Any], tool_call_id: str) -> Message:
        city = parameters.get("city")
        # 这里可以调用实际的天气API获取数据,以下是模拟数据
        weather_info = f"{city}的天气是晴朗,温度25°C。"
        return Message(
            content=weather_info,
            role="tool",
            tool_call_id=tool_call_id
        )
    
    def get_parameters(self) -> ToolParameters:
        return ToolParameters(
            type="object",
            properties={
                "city": ToolProperty(
                    type="string",
                    description="要查询天气的城市名称"
                )
            },
            required=["city"]
        )

自定义有状态Tool

如果你想自定义的Tool是有状态的,除了实现run()get_parameters(),对于有状态属性,你需要通过_get_state()方法设置和修改它们,比如

python
from violet_agents import Tool, Message, ToolParameters, ToolProperty
from typing import Dict, Any

class WeatherTool(Tool):
    def __init__(self):
        super().__init__(
            name="get_weather",
            description="获取指定城市的天气信息"
        )
    
    def run(self, parameters: Dict[str, Any], tool_call_id: str) -> Message:
        city = parameters.get("city")
        # 这里可以调用实际的天气API获取数据,以下是模拟数据
        weather_info = f"{city}的天气是晴朗,温度25°C。"
        self.times += 1
        print(f"WeatherTool has been called {self.times} times.")
        return Message(
            content=weather_info,
            role="tool",
            tool_call_id=tool_call_id
        )
    
    @property
    def times(self) -> int:
        return self._get_state().get("times", 0)
    
    @times.setter
    def times(self, value: int):
        self._get_state()["times"] = value


    def get_parameters(self) -> ToolParameters:
        return ToolParameters(
            type="object",
            properties={
                "city": ToolProperty(
                    type="string",
                    description="要查询天气的城市名称"
                )
            },
            required=["city"]
        )

通过该方式,框架会自动维护可变状态属性,使其支持多线程/协程。