> For the complete documentation index, see [llms.txt](https://docs.neuron-ai.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.neuron-ai.dev/neuron-v3-zh/dai-li/tools.md).

# 工具与工具包

赋予 Agent 与你的应用上下文和服务交互的能力。

{% hint style="warning" %}

#### 编码代理技能

**`/neuron-tool`** 用于教会你的编码代理如何实现自定义工具，并在你的代理类中使用它们。

[AI 辅助开发](/neuron-v3-zh/gai-lan/agentic-development.md)
{% endhint %}

核心代理循环包括调用模型，让它选择要执行的工具，然后在不再需要工具来提供响应时结束：

<figure><img src="https://99736354-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FGHx4l2LknIex7vFIUg1R%2Fuploads%2F59ZHXSPR086dp25ry7ou%2Fneuron-tool-call.png?alt=media&amp;token=f1a11d32-9024-4efc-8f44-6c329f2d102a" alt=""><figcaption></figcaption></figure>

### 什么是工具

工具通过促进与应用服务或外部 API 的交互，使代理超越仅生成文本。

可以把工具看作 AI 代理在需要执行特定任务时可使用的特殊函数。它们通过让代理访问可在代码中调用的特定函数，来扩展其能力。

{% embed url="<https://www.youtube.com/watch?v=lI8xE-uIek8>" %}

在 [YouTubeAgent](/neuron-v3-zh/dai-li/agent.md) 示例中，我们可以定义一个工具，使代理能够获取 YouTube 视频转写，从而生成简短摘要：

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Agent\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\Tools\PropertyType;
use NeuronAI\Tools\Tool;
use NeuronAI\Tools\ToolProperty;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // 返回 AI 提供商实例（Anthropic、OpenAI、Ollama、Gemini 等）
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    protected function instructions(): string 
    {
        return new SystemMessage(<<<TEXT
            你是一名专门编写 YouTube 视频摘要的 AI 代理。
            获取 YouTube 视频的 URL，或者让用户提供一个。
            使用你可用的工具来获取视频的转录内容。
            写一段摘要，不要使用列表。只使用流畅的文本。
            在摘要之后，再添加一个包含三句话的列表，作为视频中最重要的三个要点。
        TEXT);
    }
    
    protected function tools(): array
    {
        return [
            GetTranscriptionTool::make('API_KEY'),
        ];
    }
}

```

我们在 Agent 类中引入了新方法 `tools()` 。该方法应返回一个 AI 在需要完成任务时可使用的 Tool 数组。

Neuron 为你提供这些清晰简单的 API，并自动处理与 LLM 的所有底层交互。你几乎可以把任何想要的东西连接到 Agent。能够执行本地函数意味着你可以调用任何外部 API 或应用组件，并让 Agent 在你的环境中执行真实操作。

### 创建工具

工具是扩展 `Tool` 类的组件。你可以自由创建预打包工具，使代理能够执行操作。

要创建一个新工具，请运行下面的控制台命令：

{% tabs %}
{% tab title="Unix" %}

```bash
vendor/bin/neuron make:tool App\\Neuron\\GetTranscriptionTool
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:tool App\Neuron\GetTranscriptionTool
```

{% endtab %}
{% endtabs %}

你可以使用下面的代码自定义工具的脚手架：

```php
<?php

namespace App\Neuron\Tools;

use GuzzleHttp\Client;
use NeuronAI\Tools\PropertyType;
use NeuronAI\Tools\Tool;
use NeuronAI\Tools\ToolOutput;
use NeuronAI\Tools\ToolProperty;

class GetTranscriptionTool extends Tool
{
    protected string $name = 'get_transcription';
    
    protected ?string $description = '获取 YouTube 视频的转写内容。';
    
    protected Client $client;
    
    public function __construct(protected string $key)
    {
    }
    
    /**
     * 返回属性列表。
     */
    protected function properties(): array
    {
        return [
            new ToolProperty(
                name: 'video_url',
                type: PropertyType::STRING,
                description: 'YouTube 视频的 URL。',
                required: true
            )
        ];
    }
    
    /**
     * 实现工具逻辑
     */
    public function __invoke(string $video_url): ToolOutput
    {
        $response = $this->getClient()
            ->get('transcript?url=' . $video_url.'&text=true')
            ->getBody()
            ->getContents();

        $response = json_decode($response, true);

        return ToolOutput::text($response['content']);
    }
    
    protected function getClient(): Client
    {
        return $this->client ??= new Client([
            'base_uri' => 'https://api.supadata.ai/v1/youtube/',
            'headers' => [
                'x-api-key' => $this->key,
            ]
        ]);
    }
}
```

**工具名称和描述**：这些信息会被 LLM 用来决定何时以及为何应使用该工具。提示工程可以帮助模型做出更好的决策。

**properties 方法**：实现此方法以返回工具所期望的属性列表。

**该 `__invoke` 方法**：在这里你需要实现工具逻辑，并返回一个结果供模型继续使用。

请注意 `__invoke()` 方法接受与 `ToolProperty` 定义的、具有相同名称和数据类型的参数。

实现后，你可以在 tools 方法返回的数组中列出该工具。

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;
use App\Neuron\Tools\GetTranscriptionTool;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {...}
    
    protected function instructions(): string
    {...}
    
    protected function tools(): array
    {
        return [
            GetTranscriptionTool::make('API_KEY'),
        ];
    }
}
```

`GetTranscriptionTool 只是一个示例。你最终可以实现其他工具，使代理能够检索其他视频元数据，从而增强其视频分析能力。` 是一个示例。你最终可以实现其他工具，使代理能够检索其他视频元数据，从而增强其视频分析能力。

最后，你可以向代理提问，获取某个 YouTube 视频的摘要。

```php
use NeuronAI\Chat\Messages\UserMessage;

$message = YouTubeAgent::make($user)
    ->setThreadId('chat_id')
    ->chat(
        new UserMessage('这个视频怎么样：https://www.youtube.com/watch?v=WmVLcj-XKnM')
    )
    ->getMessage();
    
echo $message->getContent();

/**

根据转写内容，我将提供这段强有力的环境信息的摘要 
来自“母亲自然”的信息：
此视频介绍了……

三个最重要的要点：

1. 自然已经存在……

2. 人类的福祉……

3. 人类选择如何对待自然，将决定……

*/
```

### 最大运行次数

代理具有一个安全机制，用于跟踪在一次执行会话中工具被调用的次数。如果代理超过此限制，执行将被中断，并抛出 `ToolRunsExceededException` 。默认限制为 10 次调用，并且会按每个工具分别计数。

你可以通过 `toolMaxRuns()` 方法在代理级别进行自定义，或者在工具级别使用 `setMaxRuns()` 。 **为单个工具设置最大尝试次数会优先于全局设置**.

```php
try {

    $response = YouTubeAgent::make()
        ->setThreadId('chat_id')
        ->toolMaxRuns(5) // 每个工具的最大调用次数
        ->addTool(
            // 工具级别配置优先于全局设置
            CustomTool::make()->setMaxRuns(2)
        )
        ->chat(...)
        ->getMessage();
        
} catch (ToolMaxTriesException $exception) {
    // 执行某些操作
}
```

默认情况下，工具运行会按工具名称进行跟踪。你可以通过实现以下方法来自定义此键： `getRunKey()` 在工具类中：

```php
class GetTranscriptionTool extends Tool
{
    ...
    
    public function getRunKey(): string
    {
        return $this->getName() . ':' . hash('sha1', json_encode($this->getInputs()));
    }
}
```

在前面的示例中，我们还根据输入参数跟踪工具运行，因此，同一个工具在不同输入下调用时，会按不同键分别计数。对于这种特定用例，你可以使用内置的 trait `NeuronAI\Tools\TrackByInputs` ，它已经实现了基于参数感知的运行跟踪：

```php
use NeuronAI\Tools\TrackByInputs;

class GetTranscriptionTool extends Tool
{
    use TrackByInputs;
    
    ...
}
```

### 可见性

你可以根据自定义规则来控制工具的可用性。Tool 类为你提供了 `visible` 方法，用于确定代理是否甚至应该知道此工具的存在：

```php
class YouTubeAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            GetTranscriptionTool::make('API_KEY')->visible(
                auth()->user()->can(...)
            ),
        ];
    }
}
```

如果 `visible` 方法返回 `false`，则该工具在代理执行期间将不可用。

### 多模态工具输出

默认情况下，工具返回一个字符串（或一个会被 JSON 编码的数组）。当你的工具需要向模型返回更丰富的内容，如图片、文档、音频、视频时，请改为返回一个 `ToolOutput` 实例， `__invoke()` 它封装了一组内容块，支持多模态工具结果的提供方（如 Anthropic）会原生映射到其 API；仅支持文本的提供方会自动回退为连接后的文本块。所有工具开箱即用。

```php
use NeuronAI\Chat\Enums\MediaType;
use NeuronAI\Chat\Enums\SourceType;
use NeuronAI\Chat\Messages\ContentBlocks\ImageContent;
use NeuronAI\Chat\Messages\ContentBlocks\TextContent;
use NeuronAI\Tools\ToolOutput;

class PriceChartTool extends Tool
{
    ...

    public function __invoke(string $symbol): ToolOutput
    {
        $base64 = $this->renderChart($symbol);

        return new ToolOutput([
            new TextContent("{$symbol} 的价格图表"),
            new ImageContent($base64, SourceType::BASE64, MediaType::PNG),
        ]);
    }
}
```

对于单块输出，你可以使用快捷构造函数： `ToolOutput::text(...)`, `ToolOutput::image(...)`, `ToolOutput::file(...)`, `ToolOutput::audio(...)`, `ToolOutput::video(...)`.

> 在输出中包含一个 `TextContent` 块，以便在所有提供方之间通用：仅支持文本的提供方只能看到文本块，因此仅含图片的输出对它们来说会是空的。

### 工具审批

Neuron 为你提供对 human-in-the-loop 模式的完整支持，包括工具审批。框架会拦截工具调用并暂停，等待用户的最终决定。

只需覆盖 `approvalPolicy()` Tool 类中的方法，以决定该工具是否应进入审批流程。该方法会接收模型想要用于调用该工具的输入。返回字符串将被视为 true，并同时作为审批请求原因。

```php
class BuyTicketTool extends Tool
{
    ...,
    
    protected function approvalPolicy(array $inputs): bool|string
    {
        // 当金额大于 100 时，该工具需要审批
        return $inputs['amount'] > 100;
    }
}
```

请查看专门的章节，以了解如何管理完整的审批流程： [工具审批](/neuron-v3-zh/dai-li/tool-approval.md)

### 工具搜索

默认情况下，每次调用提供方时，所有工具都会被加载并传输到后端 LLM。一个连接了电子邮件、日历、云盘、CRM 以及多个 MCP 服务器的复杂生产级代理，很容易拥有数百个工具，而每个工具都带有名称、描述、参数模式和使用提示。

工具搜索将工具目录重新定义为代理按需查询的内容，而不是每次请求都随身携带的内容。

{% content-ref url="/pages/9ea2066a9f786ede6683c1553ff65e839e3105e5" %}
[中间件](/neuron-v3-zh/dai-li/middleware.md)
{% endcontent-ref %}

{% embed url="<https://www.youtube.com/watch?v=qYmidHAXEYM>" %}

### 监控与调试

你使用 Neuron 构建的许多应用都会包含多个步骤以及多次 LLM 调用。随着这些应用变得越来越复杂，能够检查你的代理系统内部究竟发生了什么变得至关重要。做到这一点的最佳方式是使用 [Inspector](https://inspector.dev/).

{% embed url="<https://docs.inspector.dev/guides/neuron-ai>" %}

## 工具属性

Neuron 允许你定义希望在工具函数中接收的数据格式。你可以将这些对象相互嵌套，以定义复杂的数据结构。

### ToolProperty

这个类表示一个简单的标量值，如字符串、整数或布尔值。

```php
namespace App\Neuron\Tools;

use NeuronAI\Tools\PropertyType;
use NeuronAI\Tools\Tool;
use NeuronAI\Tools\ToolProperty;

class MyTool extends Tool
{
    public function __construct(){...}
	
    protected function properties(): array
    {
        return [
            new ToolProperty(
                name: 'arg',
                type: PropertyType::STRING,
                description: '描述你期望的值',
                required: true
            )
        ];
    }
    
    public function __invoke(string $arg): ToolOutput 
    {
        ...
    }
}
```

### ArrayProperty

该 `ArrayProperty` 允许你要求一个具有特定特征的项目列表。

使用参数 `items` 来指定数组元素的数据类型。在下面的示例中，我们要求一个字符串数组。

```php
namespace App\Neuron\Tools;

use NeuronAI\Tools\PropertyType;
use NeuronAI\Tools\Tool;
use NeuronAI\Tools\ArrayProperty;
use NeuronAI\Tools\ToolProperty;

class MyTool extends Tool
{
    public function __construct(){...}
	
    protected function properties(): array
    {
        return [
            new ArrayProperty(
                name: 'prop_array',
                description: '描述你期望的值',
                required: true,
                items: new ToolProperty(
                    name: 'prop',
                    type: PropertyType::STRING,
                    description: '描述你期望的值',
                    required: true
                )
            )
        ];
    }
    
    public function __invoke(string $arg): ToolOutput
    {
        ...
    }
}
```

#### 最大和最小限制

ArrayProperty 还允许你使用以下参数定义期望数组大小的限制 `minItems` 和 `maxItems` 参数。

```php
$property = new ArrayProperty(
    name: "tags",
    description: "与该项目关联的标签列表",
    required: true,
    items: new ToolProperty(
        name: "tag",
        type: PropertyType::STRING,
        description: "单个标签",
        required: true
    ),
    minItems: 1,
    maxItems: 10
);
```

### ObjectProperty

类似于上面的数组示例，你可以定义一个对象数据结构：

```php
namespace App\Neuron\Tools;

use NeuronAI\Tools\PropertyType;
use NeuronAI\Tools\Tool;
use NeuronAI\Tools\ObjectProperty;
use NeuronAI\Tools\ToolProperty;

class MyTool extends Tool
{
    public function __construct(){...}
	
    protected function properties(): array
    {
        return [
            new ObjectProperty(
                name: 'colors',
                description: 'RGB 颜色',
                required: true,
                properties: [
                    new ToolProperty(
                        name: 'r',
                        type: PropertyType::NUMBER,
                        description: 'RGB 中的红色部分',
                        required: true
                    ),
                    new ToolProperty(
                        name: 'g',
                        type: PropertyType::NUMBER,
                        description: 'RGB 中的绿色部分',
                        required: true
                    ),
                    new ToolProperty(
                        name: 'b',
                        type: PropertyType::NUMBER,
                        description: 'RGB 中的蓝色部分',
                        required: true
                    )
                ]
            )
        ];
    }
    
    public function __invoke(string $arg): ToolOutput
    {
        ...
    }
}
```

### 结构化工具输入

如果你想要的对象有很多属性，你可以将一个结构化的 PHP 类传递给 `ObjectProperty` ，而不是手动定义 schema。Neuron 会将这个类的实例作为工具函数的输入参数提供给你：

```php
namespace App\Neuron\Tools;

use App\Neuron\Dto\Color;
use NeuronAI\Tools\PropertyType;
use NeuronAI\Tools\Tool;
use NeuronAI\Tools\ToolProperty;

class MyTool extends Tool
{
    public function __construct(){...}
	
    protected function properties(): array
    {
        return [
            new ObjectProperty(
                name: 'color',
                description: '颜色组合',
                required: true,
                class: Color::class
            )
        ];
    }
    
    public function __invoke(Color $color): ToolOutput
    {
        ...
    }
}
```

Color 类如下所示：

```php
<?php

namespace App\Neuron\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;

class Color
{
    #[SchemaProperty(description: "RGB 的 RED 部分", required: true)]
    public float $r;
    
    #[SchemaProperty(description: "RGB 的 GREEN 部分", required: true)]
    public float $g;
    
    #[SchemaProperty(description: "RGB 的 BLUE 部分", required: true)]
    public float $b;
}
```

## ProviderTool

某些提供方提供使用其内置工具的能力，例如 web\_search、file\_search 等，而不是依赖外部服务。即便它们提供此服务，使用这些工具也会引入许多限制。为你的代理添加能力，最灵活可靠的方式仍然是 Tools 和 Toolkit 系统。

你可以像平常一样，将提供方工具添加到代理的 tools 数组中：

```php
use NeuronAI\Tools\ProviderTool;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAIResponses(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL',
        );
    }

    protected function tools(): array
    {
        return [
            ProviderTool::make(
                type: 'web_search'
             )->setOptions([...]),
        ];
    }
}
```

目前只有 [OpenAIResponses](/neuron-v3-zh/ti-gong-shang/ai-provider.md#openairesponses), [Gemini](/neuron-v3-zh/ti-gong-shang/ai-provider.md#gemini)、以及 [Anthropic](/neuron-v3-zh/ti-gong-shang/ai-provider.md#anthropic) 支持这些工具。

## FrontendTool

到目前为止，工具一直是一个单一的东西：它是一个 PHP 函数的表示，模型可以请求运行，而后端负责执行。 `FrontendTool` 允许你定义由应用前端执行的工具。完成后，前端会返回工具执行结果，Neuron 会将该结果提供给模型，并继续循环。

这使得后端代理可以透明地访问浏览器 API，或移动应用中的设备能力。

`FrontendTool` 使这在 Neuron 中成为一等概念。模型可以看到并调用它，但后端永远不会执行它。当模型调用前端工具时，代理会暂停运行，将待处理的调用交给你，并在你提交结果后从那个点继续。

该框架为 AG-UI 提供了内置集成，支持 [CopilotKit](https://docs.copilotkit.ai/frontend-tools)、以及 [Vercel AI SDK](https://ai-sdk.dev/docs/foundations/tools#provider-defined-tools).

### 工作原理

一个 `FrontendTool` 是由名称、可选描述以及描述其输入的可选 JSON Schema 构成的。它没有 `__invoke()`；因此前端工具永远不会在服务器上运行。

当模型返回一批工具调用时，代理会将这批调用拆分：

1. **先审批。** 任何其工具需要审批的调用都会以 `ApprovalRequest`暂停运行，与本地工具完全相同。请参见 [工具审批](#tool-approval).
2. **本地工具照常在后端执行。** 在后端照常执行。
3. **延迟调用会以** 带有 `ToolResultsRequest`暂停运行。该请求携带待处理的 `ToolCall` 对象（名称、调用 ID、输入），以便你将它们分发给前端。

{% hint style="warning" %}
我们建议使用 **`/neuron-frontend-integration`** 技能在你的编码助手中，以更好地理解完整流程和实现细节。
{% endhint %}

要继续，请通过 `Agent::submitInputs()` 带有 `ToolResultsTranslator`提交按调用 ID 键控的结果。一旦每个延迟调用都有结果，代理就会将工具结果写入聊天历史并运行下一次推理。

### 注册 FrontendTool

```php
use NeuronAI\Tools\FrontendTool;

$readTitle = new FrontendTool(
    name: 'read_page_title',
    description: '读取用户当前正在查看的页面标题。'
);

$readText = new FrontendTool(
    name: 'read_element_text',
    description: '读取页面上某个元素的文本内容。',
    inputSchema: [
        'type' => 'object',
        'properties' => [
            'selector' => ['type' => 'string', 'description' => '元素的 CSS 选择器'],
        ],
        'required' => ['selector'],
    ]
);

$agent = Agent::make()
    ->setAiProvider($provider)
    ->setChatHistory(new SQLChatHistory($pdo, $threadId))
    ->setPersistence(new DatabasePersistence($pdo))
    ->addTool([$readTitle, $readText, new SearchDocsTool()]);
```

没有 schema 时，延迟工具的行为与其他工具相同：你可以使用 `addProperty()` 或者在子类中覆盖 `properties()` 。前端工具也可以像其他工具一样使用 `requireApproval()` 来要求审批。

### 带结果的暂停与恢复

```php
use NeuronAI\Agent\Interrupt\ToolResultsRequest;
use NeuronAI\Agent\Interrupt\ToolResultsTranslator;
use NeuronAI\Chat\Messages\UserMessage;

// 请求 1：模型决定读取页面。
$state = $agent->chat(new UserMessage('这个页面是关于什么的？'));

if ($state->isInterrupted()) {
    $request = $state->getInterruptRequest();

    if ($request instanceof ToolResultsRequest) {
        foreach ($request->getToolCalls() as $call) {
            // 发送 $call->getName()、$call->getCallId() 和 $call->getInputs()
            // 给将执行它们的客户端。
        }
    }
}

// 请求 2：客户端将结果发回，并以 call ID 为键。
$results = [
    'call_01' => ['result' => 'Neuron AI - PHP Agent Framework'],
    'call_02' => ['error' => '未找到元素'],
];

$state = $agent
    ->submitInputs($results, new ToolResultsTranslator())
    ->run();

echo $state->getMessage()->getContent();
```

下一次推理会将成功结果视为普通工具结果消息，并将错误视为模型可以处理的工具错误。

{% hint style="warning" %}
我们建议使用 **`/neuron-frontend-integration`** 技能在你的编码助手中，以更好地理解完整流程和实现细节。
{% endhint %}

## 工具包

Neuron 的工具包系统背后的理念，源自 AI Agent 开发中的一个基本观察：虽然单个工具提供了特定能力，但现实世界中的 AI Agent 往往需要协同的一组相关功能。

与其强迫开发者为常见用例手动组装一堆工具，Neuron 引入了工具包作为一个抽象层，改变我们对 Agent 能力组合的思考方式。下面是如何将工具包添加到 Agent 的示例：

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Calculator\CalculatorToolkit;

class MyAgent extends Agent
{
    ...
	
    protected function tools(): array
    {
        return [
            CalculatorToolkit::make(),
        ];
    }
}
```

传统做法要求逐个实例化每个工具。设想你想构建需要数学推理的 Agent——加法、减法、乘法、除法和幂运算工具都必须在 Agent 的工具配置中单独声明。当 Agent 需要全面的功能集时，这种细粒度方式很快就会变得难以管理。

工具包是 Neuron 针对这种复杂性的解决方案，它将围绕同一范围创建的工具打包成一个统一、连贯的接口，只需一行代码即可附加到任何 Agent 上。

例如：

```php
namespace NeuronAI\Tools\Toolkits\Calculator;

use NeuronAI\Tools\Toolkits\AbstractToolkit;

class BookingToolkit extends AbstractToolkit
{
    public function guidelines(): ?string
    {
        return "此工具包允许你执行数学运算。你也可以使用这些函数来求解
        数学表达式，通过逐步执行较小的运算来计算最终结果。";
    }

    public function provide(): array
    {
        return [
            Search::make(),
            Book::make(),
            Checkin::make(),
            Checkout::make(),
        ];
    }
}
```

该 `AbstractToolkit` 基类建立了一个一致的接口，所有工具包都继承它，确保整个框架中的行为可预测。

**指南**

该 `guidelines()` 此方法在 Agent 开发中具有特别重要的作用——它提供上下文信息，帮助底层语言模型不仅理解有哪些工具可用，还理解它们应如何协同使用。在 `CalculatorToolkit`的情况下，指南明确建议可以通过逐步操作来解决复杂的数学表达式，引导 Agent 采用有效的问题解决策略。

**提供**

该 `provide()` 方法默认返回工具包中包含的工具数组。当工具包附加到 Agent 时，这些单个工具就会可用，效果就像它们被分别添加过一样，但无需管理多个工具声明所带来的认知负担。

### 过滤器

在复杂 Agent 的开发过程中，我经常遇到这样的场景：某个工具包大体上提供了正确的功能，但其中包含的某些工具在特定上下文中可能导致不希望出现的行为，或者只是需要被单独限制和配置。

#### 排除

该 `exclude()` 方法优雅地解决了这一挑战，允许开发者附加全面的工具包，同时对可用能力保持细粒度控制。当处理需要特定能力的专用 Agent 时，这一点尤其有用，因为你希望降低 Agent 出错的概率，并减少 token 消耗。

```php
class MyAgent extends Agent
{
    ...
	
    protected function tools(): array
    {
    	return [
            CalculatorToolkit::make()->exclude([
                Book::class,
            ]),
        ];
    }
}
```

排除机制在类级别上运行，使用完全限定类名来识别要移除的工具。

#### 仅

同样地，你也可以使用 `only()` 来请求工具包中可用工具的一个子集。

```php
class MyAgent extends Agent
{
    ...
	
    protected function tools(): array
    {
    	return [
            CalculatorToolkit::make()->only([
                Search::class,
            ]),
        ];
    }
}
```

#### 使用

按照同样的模式，你可能需要从工具包中取回某个特定工具的实例以更改其设置。你可以使用 `with()` 方法来做到这一点。你可以传入完全限定类名来声明要获取的工具，工具实例会被注入到回调中，这样你就可以更改其设置并将其返回。

```php
class MyAgent extends Agent
{
    ...
	
    protected function tools(): array
    {
    	return [
            MySQLToolkit::make()
                ->with(
                    Book::class, 
                    fn (ToolInterface $tool) => $tool->setMaxTries(1)
                ),
        ];
    }
}
```

从可扩展性的角度来看，工具包系统为社区贡献和生态增长打开了非凡的机会。一致的接口意味着第三方开发者可以创建与 Neuron 架构无缝集成的领域特定工具包。构建金融应用 Agent 的开发者可能会创建一个 FinancialToolkit，其中包含货币换算、利息计算和风险评估工具。同样，WebScrapingToolkit 可以将 HTTP 请求工具、HTML 解析能力和数据提取工具打包成一个可复用的组件。

### 运行时添加工具

你可以向工具包中添加一个或多个工具，生成的指南或 Agent 系统指令也会涵盖它们。请注意，过滤器也会作用于已添加的工具。

```php
// 添加一个工具
MyToolkit::make()->add(new CustomTool());

// 这会将 CustomTool 从工具包中排除
MyToolkit::make()
    ->add(new CustomTool())
    ->only([AnotherTool::class]);
```

## 可用工具包

Neuron 随附了若干内置工具和工具包，可帮助你快速为 Agent 配备多种技能。你可以单独使用这些工具，也可以只用一行代码附加整个工具包。

### 计算器

该 `CalculatorToolkit` 提供了一整套计算工具，旨在帮助你的 AI Agent 准确求解数学表达式。它可以与提供数据访问的互补工具包无缝集成，例如数据库连接器、CSV 处理器、API 客户端或电子表格读取器，从而使 AI Agent 能够执行复杂的统计计算，并在面对复杂业务查询时提供全面洞见。

{% hint style="warning" %}
**`ext-bcmath`** 要使用此工具包需要 PHP 扩展。请确保将其添加为你的 `composer.json` 文件中的依赖项。
{% endhint %}

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Calculator\CalculatorToolkit;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            CalculatorToolkit::make(),
        ];
    }
}
```

<table data-header-hidden><thead><tr><th width="253"></th><th></th></tr></thead><tbody><tr><td>求值</td><td>NeuronAI\Tools\Toolkits\Calculator\Evaluate</td></tr><tr><td>平均值</td><td>NeuronAI\Tools\Toolkits\Calculator\MeanTool</td></tr><tr><td>中位数</td><td>NeuronAI\Tools\Toolkits\Calculator\MedianTool</td></tr><tr><td>众数</td><td>NeuronAI\Tools\Toolkits\Calculator\ModeTool</td></tr><tr><td>标准差</td><td>NeuronAI\Tools\Toolkits\Calculator\StandardDeviationTool</td></tr><tr><td>方差</td><td>NeuronAI\Tools\Toolkits\Calculator\VarianceTool</td></tr></tbody></table>

### 日历

此工具包提供全面的日期和时间操作。使用这些工具可以让你的 Agent 处理日期、时间、格式化、计算以及时区转换。

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\CalendarToolkit\CalendarToolkit;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            CalendarToolkit::make(),
        ];
    }
}
```

<table data-header-hidden><thead><tr><th width="205"></th><th></th></tr></thead><tbody><tr><td>当前日期时间</td><td>NeuronAI\Tools\Toolkits\Calendar\CurrentDateTimeTool</td></tr><tr><td>获取时间戳</td><td>NeuronAI\Tools\Toolkits\Calendar\GetTimestampTool</td></tr><tr><td>格式化日期</td><td>NeuronAI\Tools\Toolkits\Calendar\FormatDateTool</td></tr><tr><td>日期差异</td><td>NeuronAI\Tools\Toolkits\Calendar\DateDifferenceTool</td></tr><tr><td>添加时间</td><td>NeuronAI\Tools\Toolkits\Calendar\AddTimeTool</td></tr><tr><td>减去时间</td><td>NeuronAI\Tools\Toolkits\Calendar\SubtractTimeTool</td></tr><tr><td>计算年龄</td><td>NeuronAI\Tools\Toolkits\Calendar\CalculateAgeTool</td></tr><tr><td>转换时区</td><td>NeuronAI\Tools\Toolkits\Calendar\ConvertTimezoneTool</td></tr><tr><td>获取时区信息</td><td>NeuronAI\Tools\Toolkits\Calendar\GetTimezoneInfoTool</td></tr><tr><td>获取星期几</td><td>NeuronAI\Tools\Toolkits\Calendar\GetWeekdayTool</td></tr><tr><td>是否周末</td><td>NeuronAI\Tools\Toolkits\Calendar\IsWeekendTool</td></tr><tr><td>是否闰年</td><td>NeuronAI\Tools\Toolkits\Calendar\IsLeapYearTool</td></tr><tr><td>获取月天数</td><td>NeuronAI\Tools\Toolkits\Calendar\GetDaysInMonthTool</td></tr><tr><td>周期开始</td><td>NeuronAI\Tools\Toolkits\Calendar\StartOfPeriodTool</td></tr><tr><td>周期结束</td><td>NeuronAI\Tools\Toolkits\Calendar\EndOfPeriodTool</td></tr><tr><td>获取周数</td><td>NeuronAI\Tools\Toolkits\Calendar\GetWeekNumberTool</td></tr><tr><td>比较日期</td><td>NeuronAI\Tools\Toolkits\Calendar\CompareDatesTool</td></tr><tr><td>日期是否在范围内</td><td>NeuronAI\Tools\Toolkits\Calendar\IsDateInRangeTool</td></tr></tbody></table>

### MySQL 和 PostgreSQL

这些工具包可以让你的 Agent 与数据库交互。如果你问“作者在过去 14 天里获得了多少投票？”，Agent 不会猜测或胡乱生成答案。相反，它会识别出这个问题需要访问数据库，找出相关的表，并从你的系统中检索真实数据。

<figure><img src="https://content.gitbook.com/content/GHx4l2LknIex7vFIUg1R/blobs/se2Qip6z2M3hyHvV8rQm/data-analyst-ai-agent-php-report.png" alt=""><figcaption></figcaption></figure>

MySQL 和 PostgreSQL 工具包中的所有工具都需要一个 [PDO](https://www.php.net/manual/en/class.pdo.php) 实例作为构造函数参数。如果你处于框架环境中，或者已经在使用 ORM，那么你可以从 ORM 中获取底层 PDO 实例并将其传递给这些工具。你可以在这篇深入文章中了解更多关于这种实现策略的信息： <https://inspector.dev/mysql-ai-toolkit-bringing-intelligence-to-your-database-layer-in-php/>

PDO 实例本质上就是与某个特定数据库的连接，所以你也可以考虑为你的 Agent 创建专用凭据。这有助于控制 Agent 对数据库的访问级别。

另外，你有用于读取和写入数据库的独立工具。如果你对 Agent 的行为没有把握，可以不提供写入工具。

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\MySQL\MySQLToolkit;
use NeuronAI\Tools\Toolkits\MySQL\PGSQLToolkit;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            // 连接到 MySQL 数据库
            MySQLToolkit::make(
                new \PDO("mysql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            ),
            
            // 或 Postgre 数据库
            PGSQLToolkit::make(
                new \PDO("pgsql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            ),
        ];
    }
}
```

{% hint style="warning" %}
这些示例指的是 `MySQLToolkit` ，但使用 `PGSQLToolkit`.
{% endhint %}

#### 时也是完全相同的

该工具可帮助 Agent 理解数据库的结构，使其无需在提示词中硬编码表结构或关系即可构建智能查询。它本质上赋予你的 Agent 类似数据库管理员对模式的理解，使其能够编写尊重数据模型并利用现有索引和关系的查询。

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\MySQL\MySQLSchemaTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            MySQLSchemaTool::make(new \PDO(...)),
            
            // PGSQLSchemaTool::make(new \PDO(...)),
        ];
    }
}
```

此工具还接受第二个参数 `$tables`。你基本上可以传入一个表列表，用于限定传递给 LLM 的模式信息范围。这基本上是一种限制 Agent 之后将在数据库上执行的查询范围的方法。

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\MySQL\MySQLSchemaTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            MySQLSchemaTool::make(
                new \PDO(...),
                ['users', 'categories', 'articles', 'tags']
            ),
        ];
    }
}
```

通过限制模式范围，你可以创建专注于应用程序特定区域的专用 Agent。内容管理 Agent 可能只需要访问 articles、categories 和 tags，而用户管理 Agent 则需要看到 users、roles 和 permissions 表。此方法不仅提升性能，也降低了语言模型的认知负担，从而带来更准确、更聚焦的响应。

#### MySQLSelectTool / PGSQLSelectTool

使用此工具可让你的 Agent 能够对数据库执行 SELECT 查询。

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\MySQL\MySQLSchemaTool;
use NeuronAI\Tools\Toolkits\MySQL\MySQLSelectTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            MySQLSchemaTool::make(new \PDO(...)),
            MySQLSelectTool::make(new \PDO(...)),
        ];
    }
}
```

#### MySQLWriteTool / PGSQLWriteTool

使用此工具可让你的 Agent 能够对数据库执行写入操作（INSERT、UPDATE、DELETE）。

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\MySQL\MySQLSchemaTool;
use NeuronAI\Tools\Toolkits\MySQL\MySQLWriteTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            MySQLSchemaTool::make(new \PDO(...)),
            MySQLWriteTool::make(new \PDO(...)),
        ];
    }
}
```

### 文件系统

此工具包使 Agent 能够与本地文件系统交互。

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\FileSystem\FileSystemToolkit;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            FileSystemToolkit::make(),
        ];
    }
}
```

<table data-header-hidden><thead><tr><th width="256"></th><th></th></tr></thead><tbody><tr><td>描述目录内容</td><td>NeuronAI\Tools\Toolkits\FileSystem\DescribeDirectoryContentTool</td></tr><tr><td>读取文件</td><td>NeuronAI\Tools\Toolkits\FileSystem\ReadFileTool</td></tr><tr><td>搜索文件内容</td><td>NeuronAI\Tools\Toolkits\FileSystem\GrepFileContentTool</td></tr><tr><td>匹配路径</td><td>NeuronAI\Tools\Toolkits\FileSystem\GlobPathTool</td></tr><tr><td>预览文件</td><td>NeuronAI\Tools\Toolkits\FileSystem\PreviewFileTool</td></tr><tr><td>解析文件</td><td>NeuronAI\Tools\Toolkits\FileSystem\ParseFileTool</td></tr></tbody></table>

### Tavily

此工具包使你的 Agent 能够执行网页搜索、页面内容提取和爬取。

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Tavily\TavilyToolkit;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            TavilyToolkit::make(
                key: 'TAVILY_API_KEY'
            ),
        ];
    }
}
```

#### Tavily 网络搜索

它使你的 Agent 能够搜索网页。它需要访问 [Tavily API](https://tavily.com/).

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Tavily\TavilySearchTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            TavilySearchTool::make(
                key: 'TAVILY_API_KEY'
            ),
        ];
    }
}
```

你可以通过在 `withOptions` 方法：

```php
TavilySearchTool::make(
    key: 'TAVILY_API_KEY'
)->withOptions([
    'days' => 30,
    'max_results' => 10,
]),
```

#### Tavily 提取

从 URL 中提取网页内容。它需要访问 [Tavily API](https://tavily.com/).

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Tavily\TavilyExtractTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            TavilyExtractTool::make(
                key: 'TAVILY_API_KEY'
            ),
        ];
    }
}
```

#### Tavily 爬取

Tavily Crawl 是一种基于图的网站遍历工具，可通过内置提取和智能发现并行探索数百条路径。

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Tavily\TavilyCrawlTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            TavilyCrawlTool::make(
                key: 'TAVILY_API_KEY'
            ),
        ];
    }
}
```

### Jina

此工具包使你的 Agent 能够执行网页搜索，并读取特定 URL 的内容。

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Jina\JinaToolkit;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            JinaToolkit::make(
                key: 'JINA_API_KEY'
            ),
        ];
    }
}
```

#### Jina 网络搜索

它使你的 Agent 能够搜索网页。它需要访问 [Jina API](https://jina.ai/).

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Jina\JinaWebSearch;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            JinaWebSearch::make(
                key: 'JINA_API_KEY'
            ),
        ];
    }
}
```

#### Jina URL 读取器

从 URL 中提取网页内容。它需要访问 [Jina API](https://jina.ai/).

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Jina\JinaUrlReader;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            JinaUrlReader::make(
                key: 'JINA_API_KEY'
            ),
        ];
    }
}
```

## 并行工具调用

如果你的 Agent 很依赖工具，你可以在模型在一次请求中要求多个工具调用时启用并行执行。

#### 顺序执行（标准）

Agent 会 **一次调用一个工具**，并在开始下一个之前等待每个工具完成：

```
1. 调用工具 A → 等待结果
2. 调用工具 B → 等待结果  
3. 调用工具 C → 等待结果

总耗时：Time(A) + Time(B) + Time(C)
```

#### 并行执行（使用 `pcntl`)

Agent 会同时调用 **多个工具**，让它们同时运行：

```
1. 一次调用工具 A、B 和 C
2. 等待全部完成

总耗时：Max(Time(A), Time(B), Time(C))
```

### 要求

要使用此功能，你需要安装 `spatie/fork` 包。更多信息请查看 GitHub 仓库： <https://github.com/spatie/fork>

```shellscript
composer require spatie/fork
```

{% hint style="warning" %}

### 限制

此实现需要 `pcntl` 扩展，它在许多 Unix 和 Mac 系统上默认已安装。

**pcntl 仅适用于 CLI 进程，不适用于 Web 上下文。**

如果 `pcntl` 如果运行 Agent 的系统中不存在该扩展（例如 Windows 机器），该 trait 会自动回退到标准工具调用执行。这在你的本地开发环境与生产环境不匹配时会很有帮助。你可以在本地开发时使用 `pcntl` 已禁用，然后部署到可能已启用它的生产环境中——**无需修改任何代码**. 代理会自动适应其所处的任何执行环境。
{% endhint %}

### 启用并行执行

设置 `parallelToolCalls(true)` 到你的 Agent 或 RAG 中。框架将注入专用节点 `ParallelToolNode` 而不是标准的 `ToolNode` 到工作流中。

```php
class DemoAgent extends Agent
{
    public function __construct()
    {
        parent::__construct();
        
        // 启用并行工具调用
        $this->parallelToolCalls(
            enabled: true,
            beforeChild: fn() => DB::purge(),
            afterChild: fn() => DB::purge()
        );
    }
    
    protected function provider(): AIProviderInterface
    {
        ...
    }

    protected function tools(): array
    {
        return [
            CalculatorToolkit::make(),
        ];
    }
}
```

该方法接受两个可选回调： `beforeChild`, `afterChild`.

分叉出的子进程可能会继承无法安全共享的进程绑定资源，例如数据库连接。

## 错误处理器

现在的问题是如何处理工具错误。针对不同的场景和需求，有几种可选方案。

该 `ToolNode` 接受一个 `$errorHandler` 参数 [（代码）](https://github.com/neuron-core/neuron-ai/blob/3.x/src/Agent/Nodes/ToolNode.php#L37). 它是一个回调，会接收工具抛出的异常以及失败工具的实例。

它允许你在发生工具错误时实现自定义逻辑（一般工具异常，或 `ToolRunsExceededException`). **如果你返回一个值，它会作为工具结果返回给模型。** 默认情况下，ToolNode 会重新抛出执行错误。

**流式定义：**

```php
$agent = Agent::make()
    ->toolErrorHandler(
        fn(Throwable $e, ToolInterface $tool): string => "错误：{$e->getMessage()}"
    );
```

**扩展 Agent**

你也可以直接实现 `resolveToolErrorHandler()` 来直接定义要运行的回调。

```php
class MyAgent extends Agent
{
    ...

    protected function resolveToolErrorHandler(): ?callable
    {
        return fn(Throwable $e, ToolInterface $tool): string => "错误：{$e->getMessage()}";
    }
}
```
