# Introduction

Learn what NeuronAI is and what you can do with it.

NeuronAI is a framework that helps you create AI Agents in PHP faster and with less effort using the technology you already use and love.

Neuron makes AI Agents development accessible, reliable, and scalable. If you're just taking your first steps into AI Agents development, Neuron's extensive documentation, [guides](/v1/getting-started/fast-learning-by-video) and [tutorials](/v1/getting-started/fast-learning-by-video) will help you delve deeper into core concepts at your pace.

<figure><img src="/files/b2FsRgIOpkKz6rKnLdis" alt=""><figcaption><p>NeuronAI architecture</p></figcaption></figure>

### Developer Experience

Neuron's architecture prioritizes the fundamentals that experienced engineers expect from production-grade software. The framework leverages PHP 8's mature type system throughout its codebase, with every method signature, property, and return value explicitly typed.

***

#### Strong Typing System

This isn't just documentation—it's enforced contract specification that catches integration errors at development time rather than in production. The entire framework passes PHPStan 100% type coverage, ensuring that type safety extends beyond basic scalar types to complex object relationships and generic collections.

***

#### Well Defined Interfaces

The components architecture follows strict interface segregation principles, where each component operates through well-defined contracts. This design enables you to swap implementations without cascading changes through your application. Need to switch from OpenAI to Anthropic? Change the AI provider. Want to move from in-memory to SQL-backed conversation storage? Swap the chat history component. The interface boundaries make these transitions straightforward rather than architectural overhauls.

***

#### IDE Friendly

The strongly-typed approach means your IDE can provide accurate autocompletion for agent configurations, tool parameters, and response handling. Method signatures include detailed PHPDoc annotations that provide context beyond type hints when needed, explaining parameter expectations and return value structures.

***

This foundation translates to faster debugging cycles, easy integration patterns with frameworks like Symfony or Laravel, and the ability to confidently refactor AI logic as requirements evolve. We assume you're building systems that need to be maintained, extended, and understood by teams rather than individual experiments.

### Agnostic Architecture

One of Neuron's fundamental design principles is complete framework independence. Unlike many PHP libraries that tie themselves to specific frameworks like Laravel or Symfony, Neuron operates as a standalone component that integrates seamlessly with any PHP stack.

Whether you're working within a Laravel application, a Symfony project, a WordPress plugin, or a custom MVC framework, Neuron integrates seamlessly with your existing codebase without refactoring or disrupting established workflows.

The framework-agnostic approach extends to dependency management as well. Neuron uses standard PSR interfaces where appropriate and maintains minimal external dependencies, avoiding dependencies conflit across different PHP environments and framework versions. This design choice prevents the common problem where introducing a new library requires adopting an entire framework's ecosystem or waiting for dependencies update before moving to a new version.

For teams working across multiple projects, this approach provides consistency. The same Neuron patterns and implementations work regardless of whether you're building a new microservice in Slim, extending a WordPress site, or adding features to an enterprise Symfony and Laravel application. Knowledge transfer between projects becomes seamless, and developers can leverage their Neuron expertise across their entire PHP portfolio.

### Community Driven

These design principles create a unified ecosystem for AI development across all PHP communities. Rather than fragmenting innovation across framework-specific solutions, Neuron enables collaboration between Laravel developers, Symfony contributors, WordPress plugin authors, and custom framework maintainers. When improvements are made to Neuron's core capabilities, they benefit every PHP developer regardless of their architectural preferences.

The result is a larger, more diverse community working toward common goals. Framework-specific AI libraries naturally limit their contributor base to developers familiar with that particular framework. Neuron's universal approach attracts contributors from across the PHP ecosystem, leading to more robust implementations, broader testing across different environments, and faster development of new features. This collaborative approach also means better support for newcomers, as experienced developers from various PHP backgrounds can provide guidance and assistance regardless of which framework someone happens to be learning alongside Neuron.

### Production Readiness

Integrating AI Agents into your application you’re not working only with functions and deterministic code, you program your agent also influencing probability distributions. Same input ≠ output. That means reproducibility, versioning, and debugging become real problems.

The [Inspector](https://inspector.dev/) team designed NeuronAI with built-in observability features, so you can monitor AI agents were running, helping you maintain production-grade implementations with confidence.

Error handling and retry mechanisms are built into the framework, ensuring your agents can gracefully handle failures, rate limits, and other common issues in production environments.

### Support For Multiple Providers

Neuron uses a common interface for large language models (`AIProviderInterface`) as well as for the other components, such as [embedding](/v1/components/embeddings-provider), [vector stores](/v1/components/vector-store), [toolkits](/v1/getting-started/tools#toolkits-composable-agent-capabilities), etc. The modular architecture allows you to swap components as needed, whether you're changing language model providers, adjusting memory backends, or scaling across multiple servers.

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

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Ollama" %}

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Ollama\Ollama;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="OpenAI" %}

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\OpenAI;

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

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Gemini" %}

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Gemini;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Gemini(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_MODEL',
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

{% endtab %}
{% endtabs %}

Check out all the supported providers in the [AI Provider](/v1/components/ai-provider) section.

## What is an AI Agent

An AI agent is a software component whose output is generated by an Artificial Intelligence. These components can understand and respond to customer inquiries without human intervention. They are created using an agent development kit, like NeuronAI, to connect additional components and handle a wide range of tasks. These intelligent agents can include anything from answering simple questions to resolving complex issues that require reasoning, decision making, and proactive interactions with external systems.

Compared to a raw LLM (that primarily provide information and respond to questions within a conversation), AI agents can augment their knowledge with external sources, and take independent actions to complete tasks.

While a simple LLM can answer your questions directly during a conversation, an AI agent might be able to:

* Research information across multiple websites and compile it for you
* Manage your email by responding to simple messages
* Read data from your database and alert you via email when something important happens

The key characteristic that distinguishes agents from traditional software is their ability to operate with incomplete information and adapt to changing requirements.

### Why Build AI Agents in PHP?

PHP remains one of the most widely deployed server-side languages, powering the majority of web applications worldwide. If you're already working with PHP, Neuron allows you to integrate AI capabilities directly into your existing codebase without learning new languages or restructuring your applications. This approach significantly reduces the barrier to entry for adding intelligence to web app, content management systems, e-commerce platforms, and business backend applications.

Modern PHP offers robust object-oriented programming features, strong typing capabilities, and excellent performance characteristics that make it well-suited for AI Agents development. The language's mature ecosystem, and straightforward deployment model provide a solid foundation for building reliable agentic systems.

### Getting Started With A Video Tutorial

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

## Ecosystem

### [E-Book - "Start With AI Agents In PHP"](https://www.amazon.it/dp/B0F1YX8KJB)

The gap between modern agentic technologies and traditional PHP development has been widening in recent years. While Python developers enjoy a wealth of libraries and frameworks to create AI Agents, PHP developers have often been left wondering how they can participate in this technological revolution without completely retooling their skillsets or rebuilding their applications from scratch.

Neuron changes all that.

This book serves as both an introduction to AI Agents concepts for developers and a comprehensive guide to Neuron framework.

<https://www.amazon.it/dp/B0F1YX8KJB>

### [Newsletter](https://neuron-ai.dev)

Register to the Neuron internal [newsletter](https://neuron-ai.dev/) to get informative papers, articles, and best practices on how to start with AI development in PHP.

You will learn how to approach AI systems in the right way, understand the most important technical concepts behind LLMs, and how to start implementing your AI solutions into your PHP application with the Neuron AI framework.

### [Forum](https://github.com/inspector-apm/neuron-ai/discussions)

We’re using [Discussions](https://github.com/inspector-apm/neuron-ai/discussions) as a place to connect with PHP developers working on Neuron to create their Agentic applications. We hope that you:

* Ask questions you’re wondering about.
* Share ideas.
* Engage with other community members.
* Welcome others and are open-minded.

### [**Inspector.dev**](https://inspector.dev)

Neuron is part of the Inspector ecosystem as a trustable platform to create reliable and scalable AI driven solutions.

Trace and evaluate your agents execution flow to help you maintain production grade implementations with confidence. Check out the [**observability integrations**](/v1/advanced/observability).

## Core components

* [**AI Provider**](/v1/components/ai-provider)
* [**Toolkit**](/v1/getting-started/tools#toolkits-composable-agent-capabilities)
* [**Embeddings Provider**](/v1/components/embeddings-provider)
* [**Data Loader**](/v1/components/data-loader)
* [**Vector Store**](/v1/components/vector-store)
* [**Chat History**](/v1/components/chat-history-and-memory)
* [**MCP connector**](/v1/advanced/mcp-connector)
* [**Observability**](/v1/advanced/observability)
* [**Post Processors**](/v1/components/pre-post-processor)
* [**Workflow**](/v1/workflow/getting-started)

## Additional Resources

* Repository: <https://github.com/inspector-apm/neuron-ai>
* Inspector: <https://inspector.dev>
* E-Book: <https://www.amazon.it/dp/B0F1YX8KJB>


# Fast Learning by Video

Position yourself in the AI Agent era with our extensive tutorials and technical insights into NeuronAI capabilities. Learn from practical examples and real-world use cases.

## Video Tutorials

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

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

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

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

## Agent Development

[PHP, the Dark Horse No One Saw Coming In AI Agents development](https://inspector.dev/php-the-dark-horse-no-one-saw-coming-in-ai-agents-development/)

[LangChain alternative for PHP developers](https://inspector.dev/langchain-alternative-for-php-developers/)

[System Prompt for AI Agents In PHP](https://inspector.dev/system-prompt-for-ai-agents-in-php/)

[AI Agents Memory And Context Window In PHP](https://inspector.dev/ai-agents-memory-and-context-window-in-php/)

[Create AI Agents In PHP Powered By Google Gemini LLMs](https://inspector.dev/create-ai-agents-in-php-powered-by-google-gemini-llms/)

## RAG (Retrieval Augmented Generation)

[How to Create a RAG Agent with Neuron ADK for PHP](https://inspector.dev/how-to-create-a-rag-agent-with-neuron-adk-for-php/)

[Vector Store & AI Agents – Beyond The Traditional Data Storage](https://inspector.dev/vector-store-ai-agents-beyond-the-traditional-data-storage/)

[Improve PHP AI Agents output quality with Rerankers](https://inspector.dev/improve-php-ai-agents-output-quality-with-rerankers/)

## Tools & Toolkits

[Introducing Toolkits: Composable AI Agent Capabilities In PHP](https://inspector.dev/introducing-toolkits-composable-ai-agent-capabilities-in-php/)

[Create A Data Analyst Agent In PHP – NeuronAI MySQL Toolkit](https://inspector.dev/mysql-ai-toolkit-bringing-intelligence-to-your-database-layer-in-php/)

[Introducing Web Search Capabilities For PHP AI Agents](https://inspector.dev/introducing-web-search-capabilities-for-php-ai-agents/)

[Introducing Vision Capabilities for PHP AI Agents](https://inspector.dev/introducing-vision-capabilities-for-php-ai-agents/)

[AI Agents in PHP with MCP (Model Context Protocol)](https://inspector.dev/ai-agents-in-php-with-mcp-model-context-protocol/)

## Workflow

[Introducing NeuronAI Workflow: The future of agentic PHP applications](https://inspector.dev/introducing-neuronai-workflow-the-future-of-agentic-php-applications/)

## E-Book

The gap between modern agentic technologies and traditional PHP development has been widening in recent years. While Python developers enjoy a wealth of libraries and frameworks to create AI Agents, PHP developers have often been left wondering how they can participate in this technological revolution without completely retooling their skillsets or rebuilding their applications from scratch.

NeuronAI changes all that.

As a PHP developer, you now stand at a unique intersection of technologies. For years, PHP has powered a substantial portion of the web. Now, with Neuron AI, you can acquire the ability to infuse these web experiences with artificial intelligence, without leaving the language and ecosystem you know and love.

Neuron is the most advanced PHP framework to build AI driven features into existing PHP applications. This book serves as both an introduction to AI Agents concepts for developers and a comprehensive guide to Neuron Agent Development Kit.

Get it from [Amazon](https://www.amazon.com/dp/B0F1YX8KJB) or [Google Play](https://play.google.com/store/books/details?pcampaignid=books_read_action\&id=agJPEQAAQBAJ\&pli=1).

<figure><img src="/files/wGFIH6pUqKMfsAUF56ZF" alt="" width="375"><figcaption></figcaption></figure>


# Getting Started

Step by step instructions on how to install NeuronAI in your application and create an Agent.

### Requirements

* PHP: ^8.1

### Install

Run the composer command below to install the latest version:

```bash
composer require inspector-apm/neuron-ai
```

### Inspector

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls, tools, external memory system, etc. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

### Create an Agent

You can easily create your first agent extending the `NeuronAI\Agent` class:

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
}
```

### Talk to the Agent

Send a prompt to the agent to get a response from the underlying LLM:

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

$response = MyAgent::make()->chat(
    new UserMessage("Hi, Who are you?")
);
    
echo $response->getContent();

// I'm a friendly AI Agent built with NeuronAI, how can I help you today?
```


# Agent

Easily implement LLM interactions extending the basic Agent class.

You can create your agent by extending the `NeuronAI\Agent` class to inherit the main features of the framework and create fully functional agents. This class automatically manages some advanced mechanisms for you such as memory, tools and function calls, up to the RAG systems. We will go into more detail about these aspects in the following sections.

This implementation strategy ensures the portability of your agent because all the moving parts are encapsulated into a single entity that you can run wherever you want in your application, or even release as stand alone composer packages.

Let's start creating an AI Agent summarizing YouTube videos. We start creating the `YouTubeAgent` class extending `NeuronAI\Agent`:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        ...
    }
}
```

### Inspector

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

### AI Provider

The minimum implementation requires assigning an AI Provider that will be the language and reasoning engine of your agent.

The only required method to implement is `provider()` returning the instance of the provider you want to use. Let's assume it's Anthropic.

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider (Anthropic, OpenAI, Gemini, Ollama, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
}
```

You can also use other providers like OpenAI, Gemini, or Ollama if you want to run the model locally. Check out the [supported providers](/v1/components/ai-provider).

### System instructions

The second important building block is the system instructions. System instructions provide directions for making the AI ​​act according to the task we want to achieve. They are fixed instructions that will be sent to the LLM on every interaction.

That’s why they are defined by an internal method, and stay encapsulated into the agent entity. Let's implement the `instructions()` method:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider instance (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are an AI Agent specialized in writing YouTube video summaries."],
            steps: [
                "Get the url of a YouTube video, or ask the user to provide one.",
                "Use the tools you have available to retrieve the transcription of the video.",
                "Write the summary.",
            ],
            output: [
                "Write a summary in a paragraph without using lists. Use just fluent text.",
                "After the summary add a list of three sentences as the three most important take away from the video.",
            ]
        );
    }
}
```

The `SystemPrompt` class is designed to take your base instructions and build a consistent prompt for the underlying model reducing the effort for prompt engineering. The properties has the following meaning:

* **background**: Write about the role of the Agent. Think about the macro tasks it's intended to accomplish.
* **steps**: Define the way you expect the Agent to behave. Multiple steps help the Agent to act consistently.
* **output**: Define how you want the agent to respond. Be explicit on the format you expect.

We highly recommend to use the `SystemPrompt` class to increase the quality of the results, in alternative you can just return a simple string:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;

class YouTubeAgent extend Agent
{
    ...
    
    public function instructions(): string
    {
        return "You are an AI Agent specialized in writing YouTube video summaries.";
    }
}
```

### Talk to the YouTubeAgent

We are ready to test how the agent responds to our message based on the new instructions.

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

$response = YouTubeAgent::make()->chat(
    new UserMessage("Who are you?")
);
    
echo $response->getContent();
// Hi, I'm a frindly AI agent specialized in summarizing YouTube videos!
// Can you give me the URL of a YouTube video you want a quick summary of?
```

### Message

The agent always accepts input as a `Message` class, and returns Message instances.

As you saw in the example above we sent a `UserMessage` instance to the agent and it responded with an `AssistantMessage` instance. A list of assistant messages and user messages creates a chat.

We will learn more about [ChatHistory](/v1/components/chat-history-and-memory) later, but it's important to know that the unified interface for the agent input and response is the Message object.

## Fluent Agent Definition

In alternative to the single class encapsulation you can also instruct the agent inline using the fluent chain of methods:

```php
$agent = Agent::make()
    ->withAiProvider(
        new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        )
    )
    ->withInstructions(
        (string) new SystemPrompt(...)
    )
    ->addTool([...]);
    
$response = $agent->chat(new UserMessage(...));
```


# Tools

Give Agents the ability to interact with your application context and services.

Tools enable Agents to go beyond generating text by facilitating interaction with your application services, or external APIs.

Think about Tools as special functions that your AI agent can use when it needs to perform specific tasks. They let you extend your Agent's capabilities by giving it access to specific functions it can call inside your code.

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

In the [YouTubeAgent](/v1/getting-started/agent) example we can define a tool to make the Agent able to retrieve the YouTube video transcription, so it can crteate a short summary:

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\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
    {
        // return an AI provider instance (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string 
    {
        return (string) new SystemPrompt(
            background: ["You are an AI Agent specialized in writing YouTube video summaries."],
            steps: [
                "Get the url of a YouTube video, or ask the user to provide one.",
                "Use the tools you have available to retrieve the transcription of the video.",
                "Write the summary.",
            ],
            output: [
                "Write a summary in a paragraph without using lists. Use just fluent text.",
                "After the summary add a list of three sentences as the three most important take away from the video.",
            ]
        );
    }
    
    protected function tools(): array
    {
        return [
            Tool::make(
                'get_transcription',
                'Retrieve the transcription of a youtube video.',
            )->addProperty(
                new ToolProperty(
                    name: 'video_url',
                    type: PropertyType::STRING
                    description: 'The URL of the YouTube video.',
                    required: true
                )
            )->setCallable(function (string $video_url) {
                return "Video transcripton...";
            })
        ];
    }
}

```

Let’s break down the code.

We introduced the new method `tools()` into the Agent class. This method expects to return an array of Tool objects that the AI will be able to use if needed.

In this example we return an array of just one tool, named `get_transcription`.

Notice that the `ToolProperty` we define should match with the signature of the function you use as a callable. The callable gets the `$video_url` arguments, and the name of the property is exactly "video\_url".

The most important thing are the name and description you give to the tool and its properties. All these pieces of information will be passed to the LLM in natural language. The more explicit and clear you are, the more likely the LLM understands when, if, and why, it’s the case to use the tool.

Once the Agent decides to use a tool the callable function is executed. Here we can implement the logic to retrieve the video transcription and return the information back to the LLM.

Neuron provides you with these clear and simple APIs and automates all the underlying interactions with the LLM. Once you get the point it can immediately open to a possibility to connect basically everything you want to the Agent. Being able to execute local functions allows you to invoke any external APIs or application components.

## Implement Custom Tools

Thanks to the NeuronAI modular architecture, Tools are just a component of the toolkit that rely on the `ToolInterface` interface. You are free to create pre-packaged tool classes that implement common functionalities, and release them as external composer packages or submit a PR to our repository to have them integrated into the core framework.

To create a new Tool you can extend the `NeuronAI\Tools\Tool` class. The most important elements of a tool are:

**Tool name and description**: Define name and description of the tool in the tool constructor. Invest in prompt engineering to help the model take better decisions.

**The properties method**: Implement this method to return the list of properties the tool expects.

**The \_\_invoke method**: Here you need to implement the logic of the tool, and return a result that will be returned back to the model. The PHP `__invoke` magic method is used by default.

```php
<?php

namespace App\Neuron\Tools;

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

class GetTranscriptionTool extends Tool
{
    protected Client $client;
    
    public function __construct(protected string $key)
    {
        // Define Tool name and description
        parent::__construct(
            'get_transcription',
            'Retrieve the transcription of a youtube video.',
        );
    }
    
    /**
     * Return the list of properties.
     */
    protected function properties(): array
    {
        return [
            new ToolProperty(
                name: 'video_url',
                type: PropertyType::STRING,
                description: 'The URL of the YouTube video.',
                required: true
            )
        ];
    }
    
    /**
     * Implementing the tool logic
     */
    public function __invoke(string $video_url): string
    {
        $response = $this->getClient()
            ->get('transcript?url=' . $video_url.'&text=true')
            ->getBody()
            ->getContents();

        $response = json_decode($response, true);

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

Notice how the `__invoke()` method accepts the same arguments defined by the `ToolProperty` . In this example I'm using an external service to retrieve the YouTube video transcription called [Supadata.ai](https://supadata.ai/).

You can attach the tool in the agent class as usual:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\SystemPrompt;
use App\Neuron\Tools\MyCustomTool;

class YouTubeAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        // return an AI provider instance (Gemini, OpenAI, Ollama, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string
    {
        return (string) new SystemPrompt(...);
    }
    
    public function tools(): array
    {
        return [
            GetTranscriptionTool::make('API_KEY'),
        ];
    }
}
```

Transcriptions are just an example. You can eventually implement other tools to make the Agent able to retrieve other video metadata to enhance its video analysis capabilities.

Finally you can talk to the agent asking the summary of a YouTube video.

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

$response = YouTubeAgent::make($user)->chat(
    new UserMessage('What about this video: https://www.youtube.com/watch?v=WmVLcj-XKnM')
);
    
echo $response->getContent();

/**

Based on the transcription, I'll provide a summary of this powerful environmental 
message from "Mother Nature":
This video presents ...

Three most important takeaways:

1. Nature has existed ...

2. The wellbeing of humanity is ...

3. How humans choose to act toward Nature determines ...

*/
```

## Monitoring

When you provide the Agent with tools, Neuron sends their information to the LLM along with the user message.

Once the LLM reads the prompt and the information of the tools attached, it will decide if some tool can help to gather additional information to respond to the user prompt. In that case it will return a special response that contains the tools the LLM wants to call.

Neuron automatically manages this response for you, executing the callable of the tools the LLM decided to call, and return the result back to the LLM to get its final response.

<figure><img src="/files/Kx3cz2iQeF4v4ilIEGnb" alt=""><figcaption></figcaption></figure>

To watch inside this workflow you should connect your Agent to the [Inspector monitoring dashboard](https://inspector.dev/) in order to see the tool call execution flow in real-time.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

<figure><img src="/files/7WtGr2e8y9akhJKxTp4x" alt=""><figcaption></figcaption></figure>

In the image below you can see all the details about the execution of the tool to retrieve the transcription of the video:

<figure><img src="/files/hqIyyf47CPllmwy2IpDE" alt=""><figcaption></figcaption></figure>

## Define Tool Properties

Neuron allows you to define the format of the data you want to receive into the tool function. You can nest these objects inside each other to define complex data structures.

{% hint style="warning" %}
Be careful when defining complex data structures. Even the more advanced models can make mistakes with just a couple of properties. We strongly recommend to keep the input properties of your tools as simple as possible to improve reliability of your agent.
{% endhint %}

### ToolProperty

This class represent a simple scalar value like string, int, or boolean.

```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: 'Describe the value you expect',
                required: true
            )
        ];
    }
    
    public function __invoke(string $arg){...}
}
```

### ArrayProperty

The `ArrayProperty` allows you to require a list of items with specific characteristics.

Use the argument `items` to specify the data type of the array elements. In the example below we ask for an array of string.

```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: 'Describe the value you expect',
                required: true,
                items: new ToolProperty(
                    name: 'prop',
                    type: PropertyType::STRING,
                    description: 'Describe the value you expect',
                    required: true
                )
            )
        ];
    }
    
    public function __invoke(string $arg){...}
}
```

#### Max and Min limits

The ArrayProperty allows you also to define limitations about the size of the expected array using `minItems` and `maxItems` arguments.

```php
$property = new ArrayProperty(
    name: "tags",
    description: "List of tags associated with the item",
    required: true,
    items: new ToolProperty(
        name: "tag",
        type: PropertyType::STRING,
        description: "A single tag",
        required: true
    ),
    minItems: 1,
    maxItems: 10
);
```

### ObjectProperty

Similar to the array example above you can define an object data structure:

```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 color',
                required: true,
                properties: [
                    new ToolProperty(
                        name: 'r',
                        type: PropertyType::NUMBER,
                        description: 'The red part of the RGB',
                        required: true
                    ),
                    new ToolProperty(
                        name: 'g',
                        type: PropertyType::NUMBER,
                        description: 'The green part of the RGB',
                        required: true
                    ),
                    new ToolProperty(
                        name: 'b',
                        type: PropertyType::NUMBER,
                        description: 'The blue part of the RGB',
                        required: true
                    )
                ]
            )
        ];
    }
    
    public function __invoke(string $arg){...}
}
```

### Structured Tool Input

If the obect you want has many properties you can pass a structured PHP class to the `ObjectProperty` instead of defining the schema manually. Neuron will provide you with an instance of this class as the input argument of the tool function:

```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: 'Combination of colors',
                required: true,
                class: Color::class
            )
        ];
    }
    
    public function __invoke(Color $color){...}
}
```

Here is how the Colors class looks like:

```php
<?php

namespace App\Neuron\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;

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

## Toolkits: Composable Agent Capabilities

The philosophy behind Neuron's toolkit system emerged from a fundamental observation during AI Agent Development: while individual tools provide specific capabilities, real-world AI agents often require coordinated sets of related functionalities.

Rather than forcing developers to manually assemble collections of tools for common use cases, Neuron introduces toolkits as an abstraction layer that transforms how we think about agent capability composition.

The traditional approach requires instantiating each tool individually. Imagine you want to build agents that need mathematical reasoning – addition, subtraction, multiplication, division, and exponentiation tools must all be declared separately in the agent's tool configuration. This granular approach quickly becomes unwieldy when agents require comprehensive functionality sets.

Toolkits represent Neuron's solution to this complexity, packaging tools created around the same scope into a single, coherent interface that can be attached to any agent with a single line of code.

Here is an example of the `CalculatorToolkit`:

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

use NeuronAI\Tools\Toolkits\AbstractToolkit;

class CalculatorToolkit extends AbstractToolkit
{
    public function guidelines(): ?string
    {
        return "This toolkit allows you to perform mathematical operations. You can also use this functions to solve
        mathematical expressions executing smaller operations step by step to calculate the final result.";
    }

    public function provide(): array
    {
        return [
            SumTool::make(),
            SubtractTool::make(),
            MultiplyTool::make(),
            DivideTool::make(),
            ExponentiateTool::make(),
        ];
    }
}
```

The `AbstractToolkit` base class establishes a consistent interface that all toolkits inherit, ensuring predictable behavior across the framework.

The `guidelines()` method serves a particularly important function in agent development – it provides contextual information that helps the underlying language model understand not just what tools are available, but how they should be used together. In the case of the `CalculatorToolkit`, the guidelines explicitly suggest that complex mathematical expressions can be solved through step-by-step operations, guiding the agent toward effective problem-solving strategies.

The `provide()` method returns the array of tools included in the toolkit by default. When a toolkit is attached to an agent, the individual tools become available exactly as if they had been added separately, but without the cognitive overhead of managing multiple tool declarations. Here is how you can add it to your agent:

```php
<?php

namespace App\Neuron;

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

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

During development of complex agents, I've frequently encountered scenarios where a toolkit provides mostly the right functionality but includes tools that could lead to undesired behavior in specific contexts. The `exclude()` method addresses this challenge elegantly, allowing developers to attach comprehensive toolkits while maintaining fine-grained control over available capabilities. This becomes particularly useful when working with specialized agents that need specific capabilities but you want to reduce the probability of an agent mistake, and reduce tokens consumption.

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Calculator\CalculatorToolkit;
use NeuronAI\Tools\Toolkits\Calculator\DivideTool;
use NeuronAI\Tools\Toolkits\Calculator\ExponentiateTool;
use NeuronAI\Tools\Toolkits\Calculator\MultiplyTool;

class MyAgent extends Agent
{
    ...
	
    public function tools(): array
    {
    	return [
            CalculatorToolkit::make()->exclude([
                DivideTool::class,
                ExponentiateTool::class,
                MultiplyTool::class,
            ]),
        ];
    }
}
```

The exclusion mechanism operates at the class level, using fully qualified class names to identify tools for removal. In the same way you can also use the mthod `only()` to request a sub-set of the available tools in the toolkit.

```php
<?php

namespace App\Neuron;

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

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

From an extensibility perspective, the toolkit system opens remarkable opportunities for community contribution and ecosystem growth. The consistent interface means that third-party developers can create domain-specific toolkits that integrate seamlessly with Neuron's architecture. A developer building agents for financial applications might create a FinancialToolkit that includes tools for currency conversion, interest calculation, and risk assessment. Similarly, a WebScrapingToolkit could package HTTP request tools, HTML parsing capabilities, and data extraction utilities into a single, reusable component.

## Available Toolkits

Neuron ships with several built-in tools and toolkits that allows you to quickly equip your agents with many skills. You can use these tools individually or attach entire toolkits with a single line of code.

### Calculator

The CalculatorToolkit provides a comprehensive suite of computational tools designed to make your AI agents performs accurate calculations. It can seamlessly integrates with complementary toolkits that provide data access—such as database connectors, CSV processors, API clients, or spreadsheet readers—enabling AI agents to perform sophisticated statistical calculations, and deliver comprehensive insights in response to complex business queries.

```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>sum</td><td>NeuronAI\Tools\Toolkits\Calculator\SumTool</td></tr><tr><td>subtract</td><td>NeuronAI\Tools\Toolkits\Calculator\SubtractTool</td></tr><tr><td>multiply</td><td>NeuronAI\Tools\Toolkits\Calculator\MultiplyTool</td></tr><tr><td>divide</td><td>NeuronAI\Tools\Toolkits\Calculator\DivideTool</td></tr><tr><td>exponential</td><td>NeuronAI\Tools\Toolkits\Calculator\ExponentialTool</td></tr><tr><td>square root</td><td>NeuronAI\Tools\Toolkits\Calculator\SquareRootTool</td></tr><tr><td>nth root</td><td>NeuronAI\Tools\Toolkits\Calculator\NthRootTool</td></tr><tr><td>mean</td><td>NeuronAI\Tools\Toolkits\Calculator\MeanTool</td></tr><tr><td>median</td><td>NeuronAI\Tools\Toolkits\Calculator\MedianTool</td></tr><tr><td>mode</td><td>NeuronAI\Tools\Toolkits\Calculator\ModeTool</td></tr><tr><td>standard deviation</td><td>NeuronAI\Tools\Toolkits\Calculator\StandardDeviationTool</td></tr><tr><td>variance</td><td>NeuronAI\Tools\Toolkits\Calculator\VarianceTool</td></tr></tbody></table>

### Calendar

​This toolkit provides comprehensive date and time operations. Use these tools to make your agent able to work with dates, times, formatting, calculations, and timezone conversions.

```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><thead><tr><th width="205"></th><th></th></tr></thead><tbody><tr><td>current_datetime</td><td>NeuronAI\Tools\Toolkits\Calendar\CurrentDateTimeTool</td></tr><tr><td>get_timestamp</td><td>NeuronAI\Tools\Toolkits\Calendar\GetTimestampTool</td></tr><tr><td>format_date</td><td>NeuronAI\Tools\Toolkits\Calendar\FormatDateTool</td></tr><tr><td>date_difference</td><td>NeuronAI\Tools\Toolkits\Calendar\DateDifferenceTool</td></tr><tr><td>add_time</td><td>NeuronAI\Tools\Toolkits\Calendar\AddTimeTool</td></tr><tr><td>subtract_time</td><td>NeuronAI\Tools\Toolkits\Calendar\SubtractTimeTool</td></tr><tr><td>calculate_age</td><td>NeuronAI\Tools\Toolkits\Calendar\CalculateAgeTool</td></tr><tr><td>convert_timezone</td><td>NeuronAI\Tools\Toolkits\Calendar\ConvertTimezoneTool</td></tr><tr><td>get_timezone_info</td><td>NeuronAI\Tools\Toolkits\Calendar\GetTimezoneInfoTool</td></tr><tr><td>get_weekday</td><td>NeuronAI\Tools\Toolkits\Calendar\GetWeekdayTool</td></tr><tr><td>is_weekend</td><td>NeuronAI\Tools\Toolkits\Calendar\IsWeekendTool</td></tr><tr><td>is_leap_year</td><td>NeuronAI\Tools\Toolkits\Calendar\IsLeapYearTool</td></tr><tr><td>get_days_in_month</td><td>NeuronAI\Tools\Toolkits\Calendar\GetDaysInMonthTool</td></tr><tr><td>start_of_period</td><td>NeuronAI\Tools\Toolkits\Calendar\StartOfPeriodTool</td></tr><tr><td>end_of_period</td><td>NeuronAI\Tools\Toolkits\Calendar\EndOfPeriodTool</td></tr><tr><td>get_week_number</td><td>NeuronAI\Tools\Toolkits\Calendar\GetWeekNumberTool</td></tr><tr><td>compare_dates</td><td>NeuronAI\Tools\Toolkits\Calendar\CompareDatesTool</td></tr><tr><td>is_date_in_range</td><td>NeuronAI\Tools\Toolkits\Calendar\IsDateInRangeTool</td></tr></tbody></table>

### MySQL & PostgreSQL

These toolkits make your agent able to interact with your database. If you ask "How many votes did the authors get in the last 14 days?", the agent doesn’t guess or hallucinate an answer. Instead, it recognizes that this question requires database access, identifies the appropriate tables involved and retrieves real data from your system.

<figure><img src="/files/VQAI0qe7Kmt9mEawckzT" alt=""><figcaption></figcaption></figure>

All the tools in the MySQL and PostgreSQL toolkits require a [PDO](https://www.php.net/manual/en/class.pdo.php) instance as a constructor argument. If you are in a framework environment or you are already using an ORM in general, you can gather the underlying PDO instance from the ORM and pass it to the tools. You can learn more about this implementation strategy in this in-depth article: <https://inspector.dev/mysql-ai-toolkit-bringing-intelligence-to-your-database-layer-in-php/>

The PDO instance is basically a connection to a specific database, so you could aslo think to create dedicated credentials for your agent. It could be helpful to control the level of access your agent has to the database.

Anyway you have separate tools for reading and writing to the database. If you are not confident about your agent behaviour you may not provide the writing tool.

{% hint style="warning" %}
Examples below refer to the `MySQLToolkit` but it's exactly the same using `PGSQLToolkit` and indivudual tools.
{% endhint %}

```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 [
            // Connect to a MySQL database
            MySQLToolkit::make(
                new \PDO("mysql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            ),
            
            // or Postgre database
            PGSQLToolkit::make(
                new \PDO("pgsql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            ),
        ];
    }
}
```

#### MySQLSchemaTool / PGSQLSchemaTool

This tool allows agents to understand the structure of your database, enabling them to construct intelligent queries without requiring you to hardcode table structures or relationships into prompts. This tool essentially gives your agent the equivalent of a database administrator’s understanding of your schema, allowing it to craft queries that respect your data model and take advantage of existing indexes and relationships.

```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(...)),
        ];
    }
}
```

This tool also accept a second argument `$tables`. You can basically pass a list of tables that you want to include in the schema information passed to the LLM. This is basically a way to limit the scope of the queries the agent will later execute on the database.

```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']
            ),
        ];
    }
}
```

By limiting the schema scope, you can create specialized agents that focus on specific areas of your application. A content management agent might only need access to articles, categories, and tags, while a user administration agent requires visibility into users, roles, and permissions tables. This approach not only improves performance but also reduces the cognitive load on the language model, leading to more accurate and focused responses.

#### MySQLSelectTool / PGSQLSelectTool

Use this tool to make your agent able to run SELECT query against the database.

```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

Use this tool to make your agent able to performs write operations against the database (INSERT, UPDATE, DELETE).

<pre class="language-php"><code class="lang-php"><strong>namespace App\Neuron;
</strong>
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(...)),
        ];
    }
}
</code></pre>

### Tavily

This toolkit enable your agent to performs web search, page content extraction, and crawling.

```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 Web Search

It makes your Agent able to search the web. It requires access to [Tavily APIs](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'
            ),
        ];
    }
}
```

You can customize the default options to retrieve search results by passing your preference in the `withOptions` method:

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

#### Tavily Extract

Extract web page content from an URL. It requires access to [Tavily APIs](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 Crawl

Tavily Crawl is a graph-based website traversal tool that can explore hundreds of paths in parallel with built-in extraction and intelligent discovery.

```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

This toolkit enable your agent to performs web search, and read the content of a specific 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 Web Search

It makes your Agent able to search the web. It requires access to [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 Reader

Extract web page content from an URL. It requires access to [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'
            ),
        ];
    }
}
```

### Zep Memory

This toolkit connects a NeuronAI Agent to [Zep](https://www.getzep.com/) knowledge graph. This kind of system allows the agent to store relevant facts that may emerge during interactions with the agent over time. It's a long term memory in the sense that is not limited to the current conversation like the [ChatHistory](/v1/components/chat-history-and-memory) component does. It's an external persistent storage the agent will use to store and retrieve single pieces of information that can allow more personalized answers.

To learn more about the capabilities of these kind of system you can visit the Zep website: <https://www.getzep.com/>

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Zep\ZepLongTermMemoryToolkit;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            ZepLongTermMemoryToolkit::make(
                key: 'ZEP_API_KEY',
                user_id: 'ID'
            ),
        ];
    }
}
```

The `user_id` arguments allows you to separate the long term memory in different silos if you want to serve multiple users. Based on your use case you can use this parameter as a "key" to separate the memory for the various entities the agent interact to (users, companies, etc.).

### AWS SES

#### Simple Email Service (SES)

This tool allows the agent to send an email message to one or more recipients, send notifications, confirmations, reports, or any other email-based communication. The tool handles proper email delivery, and basic error handling automatically.

In order ti use this tool the AWS sdk for PHP must be installed.

```
composer require aws/aws-sdk-php
```

The tool gets an instance of the `SesClient` class from the AWS PHP sdk.

```php
namespace App\Neuron;

use Aws\Ses\SesClient;
use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\AWS\SESTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            SESTool::make(
                sesClient: new SesCleint(...),
                fromEmail: 'my-address@email.com'
            ),
        ];
    }
}
```

### Supadata YouTube

This toolkit provides access to YouTube video transcriptions, metadata, channel information,\
and playlist data through Supadata.ai for content analysis and research purposes.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataYouTubeToolkit;

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

#### Video Transcription

Allow the agent to retrieve the transcription of a youtube video.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataVideoTranscriptTool;

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

#### Video Metadata

Allow the agent to retrieve the metadata of a youtube video.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataVideoMetadataTool;

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

#### Channel Metadata

Allow the agent to retrieve metadata from a YouTube channel including name, description, subscriber count, and more.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataYoutubeChannelTool;

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

#### Playlist Metadata

Allow the agent to retrieve metadata from a YouTube playlist including title, description, video count, and more.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataYoutubePlaylistTool;

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


# Streaming

Presenting AI response to your user in real-time.

Streaming enables you to show users chunks of response text as they arrive rather than waiting for the full response. You can offer a real-time Agent conversation experience.

<figure><img src="/files/OGZswa7JMJc91CVRSvo4" alt=""><figcaption></figcaption></figure>

### Agent

To stream the AI response you should use the `stream()` method to run the agent, instead of `chat()`. This method return a PHP generator that can be used to process the response as an iterable object.

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Messages\UserMessage;

$stream = MyAgent::make()->stream(
    new UserMessage('How are you?')
);

// Print the response chunk-by-chunk in real-time
foreach ($stream as $text) {
    echo $text;
}

// I'm fine, thank you! How can I assist you today?
```

### Streaming & Tools

Neuron support Tools & Function calls in combination with the streaming response. You are free to provide your Agents with Tools and they will be automatically handled in the middle of the stream, to continue toward the final response.

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Tools\Tool;

$stream = MyAgent::make()
    ->addTool(
        Tool::make(
            'get_server_configuration',
            'retrieve the server network configuration'
        )->addProperty(...)->setCallable(...)
    )
    ->stream(
        new UserMessage("What's the IP address of the server?")
    );

// Iterate chunks
foreach ($stream as $chunk) {
    if ($chunk instanceof ToolCallMessage) {
        // Output the ongoing tool call
        echo PHP_EOL.\array_reduce(
            $chunk->getTools(), 
            fn(string $carry, ToolInterface $tool) 
                => $carry .= '- Calling tool: '.$tool->getName().PHP_EOL, 
            '');
    } else {
        echo $chunk;
    }
}

// Let me retrieve the server configuration. 
// - Calling tool: get_server_configuration
// The IP address of the server is: 192.168.0.10
```

### Monitoring

To watch inside this workflow you should connect your Agent to the [Inspector monitoring dashboard](https://inspector.dev/) in order to see the tool call execution flow in real-time.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

<figure><img src="/files/DcPKeHK77Cm2G8JaSX5Y" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Learn more about Agent observability in the [dedicated documentation](/v1/advanced/observability).
{% endhint %}


# Structured Output

Enforce the Agent output based on the provided schema.

{% hint style="info" %}
PREREQUISITES

This guide assumes you are already familiar with the following concepts:

* [Agent](/v1/getting-started/agent)
* [Tool & Function Call](/v1/getting-started/tools)
  {% endhint %}

There are many use cases where we need Agents to understand natural language, but output in a *structured format*. One common use-case is extracting data from text to insert into a database or use with some other downstream system. This guide covers how Neuron allows you to enforce structured outputs from the agent.

<figure><img src="/files/DlraJPuxbgGnWXMFHEpg" alt=""><figcaption></figcaption></figure>

### How to use Structured Output

The central concept is that the output structure of LLM responses needs to be represented in some way. The schema that Neuron validates against is defined by PHP type hints. Basically you have to define a class with strictly typed properties:

```php
<?php

namespace App\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;

class Person 
{
    #[SchemaProperty(description: 'The user name.', required: true)]
    public string $name;
    
    #[SchemaProperty(description: 'What the user love to eat.', required: false)]
    public string $preference;
}
```

Neuron generates the corresponding JSON schema from the PHP object to instruct the underlying model about your required data format. Then the agent parse the LLM output to extract data and returns an object instance filled with appropriate values:

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

// Talk to the agent requiring the structured output
$person = MyAgent::make()->structured(
    new UserMessage("I'm John and I like pizza!"),
    Person::class
);

echo $person->name.' like '.$person->preference;
// John like pizza
```

### Default output class

You can also encapsulate the output format into the Agent implementation, so it will be the Agent standard output format. You always need to call the `structured()` method to require strict output.

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

// Encapsulate the default output format 
class MyAgent extends Agent
{
    ...

    protected function getOutputClass(): string
    {
        return Person::class;
    }
}

// Always use the structured method if you want to get structured output
$person = MyAgent::make()
    ->structured(new UserMessage("I'm John and I like pizza"));

echo $person->name.' like '.$person->preference;
// John like pizza
```

### Control the output generation

Neuron requires you to define two layers of rules to create the structured output class.

The first is the `SchemaProperty` attribute that allows you to control the JSON schema sent to the LLM to understand the required data format.

The second layer is validation. Validation attributes will ensure data gathered from the LLM response are consistent with your requirements.

<figure><img src="/files/2CHIkeVRl0iN90aHfbv0" alt=""><figcaption></figcaption></figure>

### SchemaProperty

We strongly recommend to use the `SchemaProperty` attribute to define at least the description, to allow the LLM understand the purpose of a property, and the required flag:

```php
<?php

namespace App\Neuron\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;

class Person 
{
    #[SchemaProperty(description: 'The user name.', required: true)]
    public string $name;
    
    #[SchemaProperty(description: 'What the user love to eat.', required: false)]
    public string $preference;
}
```

### Validation

The Validation component already contains many validation rules that you can apply to the output class properties. The example below shows you how to mark the name property as required (*NotBlank*):

```php
<?php

namespace App\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Person 
{
    #[SchemaProperty(description: 'The user name.')]
    #[NotBlank]
    public string $name;
    
    #[SchemaProperty(description: 'What the user love to eat.')]
    public string $preference;
}
```

### Nested Class

You can construct complex output structures using other PHP objects as a property type. Following the example of a the `Person` class we can add the `address` property typed as another structured class.

```php
<?php

namespace App\Dto;

use NeuronAI\StructuredOutput\Property;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Valid;

class Person 
{
    #[SchemaProperty(description: 'The user name.', required: true)]
    #[NotBlank]
    public string $name;
    
    #[SchemaProperty(description: 'What user love to eat.', required: true)]
    public string $preference;
    
    #[SchemaProperty(description: 'The address to complete the delivery.', required: true)]
    public Address $address;
}
```

In the `Address` definition we require only the street and zip code properties, and allow city to be empty.

```php
<?php

namespace App\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Address
{
    #[SchemaProperty(description: 'The name of the street.', required: true)]
    #[NotBlank]
    public string $street;

    #[SchemaProperty(description: 'The name of the city.', required: false)]
    public string $city;

    #[SchemaProperty(description: 'The zip code of the address.', required: true)]
    #[NotBlank]
    public string $zip;
}
```

Now when you ask the agent for the structured output you will get the filled instance back:

<pre class="language-php"><code class="lang-php">use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Observability\AgentMonitoring;

<strong>// Talk to the agent requiring the structured output
</strong>$person = MyAgent::make()->structured(
    new UserMessage("I'm John and I want a pizza at st. James Street 00560!"),
    Person::class
);

echo $person->name.' like '.$person->preference.'. Address: '.$person->address->street;
// John like pizza. Address: st.James Street
</code></pre>

### Array

If you declare a property as an array Neuron assumes the list of items to be a list of string. Assume we want to add a list of tags to the Person object:

```php
<?php

namespace App\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Person 
{
    #[SchemaProperty(description: 'The user name.', required: true)]
    #[NotBlank]
    public string $name;
    
    #[SchemaPropertyerty(description: 'What user love to eat.', required: true)]
    public string $preference;
    
    #[SchemaProperty(description: 'The list of tag for the user profile.', required: true)]
    public array $tags;
}
```

Without any additional information the agent will assume that the `tags` property is an array of strings by default.

```php
echo $person->tags;

/*
[
    'tag 1',
    'tag 2',
    ...
]
*/
```

### Array of objects

It could be needed to populate the list of tags with another structured data type. To do this you must add the `ArrayOf` attribute for properly validation, and specify the fully qualified class namespace in the doc-block for properly deserialization:

```php
<?php

namespace App\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;
use NeuronAI\StructuredOutput\Validation\Rules\ArrayOf;

class Person 
{
    #[SchemaProperty(description: 'The user name.', required: true)]
    #[NotBlank]
    public string $name;
    
    #[SchemaProperty(description: 'What user love to eat.', required: true)]
    public string $preference;
    
    /**
     * @var \App\Agent\Models\Tag[]
     */
    #[SchemaProperty(description: 'The list of tag for the user profile.', required: true)]
    #[ArrayOf(Tag::class)]
    public array $tags;
}
```

And here is the hypotetical implementation of the `Tag` class with its own validation rules and property info:

```php
<?php

namespace App\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Tag
{
    #[SchemaProperty(description: 'The name of the tag', required: true)]
    #[NotBlank]
    public string $name;
}
```

### Max Retries

Since the LLM are not perfectly deterministic it's mandatory to have a retry mechanism in place if something is missing in the LLM response.

By default Neuron extracts and validates the data from the LLM response and if there is one or more validation errors automatically retry the request just one more time informing the LLM about what went wrong and for what properties.

You can eventually customize the number of times the agent must retry to get a correct answer from the LLM:

```php
$person = MyAgent::make()->structured(
    messages: new UserMessage("I'm John and I like pizza!"),
    class: Person::class,
    maxRetries: 3
);
```

If you work with a less capable LLM consider to use a number of retries balancing the probability to get e valid answer, and the potential token consumption.

You can disable retry just passing zero. It will be a one shot attempt:

```php
$person = MyAgent::make()->structured(
    messages: new UserMessage("I'm John and I like pizza!"),
    class: Person::class,
    maxRetries: 0
);
```

## Monitoring

To watch inside this workflow you should connect your Agent to the [Inspector monitoring dashboard](https://inspector.dev/) in order to see the tool call execution flow in real-time.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

<figure><img src="/files/wteFRaxd2WODZTv7YpKr" alt=""><figcaption></figcaption></figure>

Each segment bring its own debug information to follow the agent execution in real time:

<figure><img src="/files/1oEyoGAbyknqh5zS6UJF" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Learn how to enable [**observability**](/v1/advanced/observability) in the next section.
{% endhint %}

## Available Validation Rules

### #\[NotBlank]

The property under validation cannot be blank. It accept the allowNull flag to treat explicitly null value as empty equivalent or not.

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Person 
{
    #[NotBlank(allowNull: false)]
    public string $name;
}
```

### #\[Length]

This rule works only on `string` properties. The property under validation must respect the length constraints:

<pre class="language-php"><code class="lang-php"><strong>namespace App\Dto;
</strong>
use NeuronAI\StructuredOutput\Validation\Rules\Length;

class Person 
{
    #[Length(min: 1, max: 10)]
    public string $name;
    
    #[Length(exactly: 5)]
    public string $zip_code;
}
</code></pre>

### #\[Count]

This rule works only on array properties. The property under validation must have a size matching the constraint definition:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\Count;

class Person 
{
    #[Count(min: 1, max: 3)]
    public array $dogs;
    
    #[Count(exactly: 1)]
    public array $children;
}
```

### #\[EqualTo] - #\[NotEqualTo]

These rules have the same structure and meaning, and accept a single argument to define the value to compare against. The property under validation must be strictly equal (*#\[EqualTo]*) or different (*#\[NotEqualTo]*) than the reference value:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\EqualTo;
use NeuronAI\StructuredOutput\Validation\Rules\NotEqualTo;

class Person 
{
    #[EqualTo(reference: 'Rome')]
    public string $city;
    
    #[NotEqualTo(reference: '00502')]
    public string $zip_code;
}
```

### #\[GreaterThan] - #\[GreaterThanEqual]

These rules have the same structure and meaning, and accept a single argument to define the value to compare against. The property under validation must be strictly greater (*#\[GreaterThan]*) or equal (*#\[GreaterThanEqual]*) than the reference value:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\GreaterThan;
use NeuronAI\StructuredOutput\Validation\Rules\GreaterThanEqual;

class Person 
{
    #[GreaterThan(reference: 17)]
    public int $age;
    
    #[GreaterThanEqual(reference: 1)]
    public int $cars;
}
```

### #\[LowerThan] - #\[LowerThanEqual]

These rules have the same structure and meaning, and accept a single argument to define the value to compare against. The property under validation must be strictly lower (*#\[LowerThan]*) or equal (*#\[LowerThanEqual]*) than the reference value:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\LowerThan;
use NeuronAI\StructuredOutput\Validation\Rules\LowerThanEqual;

class Person 
{
    #[LowerThan(reference: 50)]
    public int $age;
    
    #[LowerThanEqual(reference: 1)]
    public int $cars;
}
```

### #\[IsFalse] - #\[IsTrue]

The property under validation must have exactly the boolean value defined by the rule:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\IsFalse;
use NeuronAI\StructuredOutput\Validation\Rules\IsTrue;

class Phone
{
    #[IsFalse]
    public bool $iphone;
    
    #[IsTrue]
    public bool $refurbed;
}
```

### #\[IsNull] - #\[IsNotNull]

The property under validation must respect the nullable condition defined by the rule:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\IsNotNull;
use NeuronAI\StructuredOutput\Validation\Rules\IsNull;

class Phone
{
    #[IsNotNull]
    public string $brand;
    
    #[IsNull]
    public ?string $test;
}
```

### #\[Json]

The property under validation must contains a valid JSON string:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\Json;

class Person
{
    #[Json]
    public string $address;
}
```

### #\[Url]

The property under validation must contains a valid URL:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\Url;

class Person
{
    #[Url]
    public string $website;
}
```

### #\[Email]

The property under validation must contains a valid Email address:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\Email;

class Person
{
    #[Email]
    public string $email;
}
```

### #\[IpAddress]

The property under validation must contains a valid IP address:

```php
namespace App\Dto;

use NeuronAI\StructuredOutput\Validation\Rules\IpAddress;

class Person
{
    #[IpAddress]
    public string $ip;
}
```

### #\[ArrayOf]

The property under validation must be an array that contains all of the given type of object. Notice that you also need to add the doc-block in order to make the agent able to instance the correct class. Use the full class namespace in the doc-block.

<pre class="language-php"><code class="lang-php"><strong>namespace App\Dto;
</strong>
use NeuronAI\StructuredOutput\Validation\Rules\ArrayOf;

class Person
{
    /**
     * @var \App\Dto\Tag[]
     */
    #[ArrayOf(Tag::class)]
    public array $tags;
}
</code></pre>


# Attachments (Documents & Images)

Attach documents and images to your message.

Most advanced LLMs can understand the content of documents and images other than simple text. With Neuron you can attach files to your messages to enrich the context provided to the Agent.

The most common use cases for documents analysis are:

* Caption and answer questions about images
* Transcribe and reason over document contents

You have two options to attach items to your messages: as an URL, or encoded in base64.

{% hint style="warning" %}
Be sure about the possible limitations of your AI provider to handle documents and images in specific formats.
{% endhint %}

## Documents

#### URL

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Attachments\Document;
use NeuronAI\Chat\Messages\UserMessage;

// Ollama only support images encoded in base64
$message = (new UserMessage("Describe this document"))
    ->addAttachment(
        new Document('https://url_of/document.pdf')
    );
    
$response = MyAgent::make()->chat($message);
// The document is a contract...
```

#### Base64

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Attachments\AttachmentContentType;
use NeuronAI\Chat\Attachments\Document;
use NeuronAI\Chat\Messages\UserMessage;

$content = base64_encode(file_get_contents('/document.pdf'));

$message = (new UserMessage("Describe this document"))
    ->addAttachment(
        new Document(
            content: $content,
            contentType: AttachmentContentType::BASE64,
            mediaType: 'application/pdf'
        )
    );
    
$response = MyAgent::make()->chat($message);
// The document is a contract...
```

## Images

#### URL

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Attachments\Image;
use NeuronAI\Chat\Messages\UserMessage;

// Ollama only support images encoded in base64
$message = (new UserMessage("Describe this image"))
    ->addAttachment(
        new Image('https://url_of/image.jpg')
    );
    
$response = MyAgent::make()->chat($message);
// The image shows...
```

#### Base64

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Attachments\AttachmentContentType;
use NeuronAI\Chat\Attachments\Image;
use NeuronAI\Chat\Messages\UserMessage;

$content = base64_encode(file_get_contents('/image.jpg'));

$message = (new UserMessage("Describe this image"))
    ->addAttachment(
        new Image(
            content: $content,
            contentType: AttachmentContentType::BASE64,
            mediaType: 'image/jpeg'
        )
    );
    
$response = MyAgent::make()->chat($message);
// The image shows...
```

## Ollama limitations

Ollama only support images in base64 format, so you have to take care to convert the file content and set up the right type for attachments:

```php
use NeuronAI\Chat\Attachments\AttachmentContentType;
use NeuronAI\Chat\Attachments\Image;
use NeuronAI\Chat\Messages\UserMessage;

// Ollama only support images encoded in base64
$message = (new UserMessage("Describe this image"))
    ->addAttachment(
        new Image(
            image: 'base64-encoded-content', 
            type: AttachmentContentType::BASE64, 
            mediaType: 'image/jpeg'
        )
    );
```


# RAG

Step by Step guide on how to implement Retrieval-Augmented Generation with Neuron AI framework.

{% hint style="info" %}
PREREQUISITES

This guide assumes you are already familiar with the following concepts:

* [Agent](/v1/getting-started/agent)
* [Tool & Function Call](/v1/getting-started/tools)
  {% endhint %}

Retrieval-Augmented Generation (RAG) is the process of providing references to a knowledge base outside of the LLM training data sources before generating a response.

Large Language Models (LLMs) are trained on vast volumes of data to be able to generate original output for tasks like answering questions, translating languages, and completing sentences. RAG extends the already powerful capabilities of LLMs to specific domains or an organization's internal knowledge base, all without the need to retrain the model.

It is a cost-effective approach to improving LLM output so it remains relevant, accurate, and useful also working on your own private data.

## Why RAG systems are relevant

Building a RAG system is the way to use the powerful LLM capabilities on your own private data. You can create applications capable of accurately answering questions about a company internal documentations. Or chatbot to serve external customers on the internal rules of an organization.

If it's not about the usage of private data, you can think of RAG as a way to provide the latest research, statistics, or news to the generative models.

## How to create a RAG system

Without RAG, the LLM takes the user input and creates a response based on information it was trained on—or what it already knows.

With RAG, an information retrieval component is introduced. It utilizes the user input to first pull information from a new data source. The user query and the relevant information retrieved are both given to the LLM. The LLM uses the new knowledge and its training data to create better responses. The following sections provide an overview of the process.

Even if it can appear a little bit complicated, don't worry, this is just to make you aware of the process. Most of these things are automatically managed by a Neuron RAG agent.

There are three most important steps to create a RAG system.

### Process external data

The external data you want to use to augment the default LLM knowledge may exist in various formats like files, database records, or long-form text.

Before being able to submit this data to the LLM you have to convert them into a specific format called "Embeddings".

### Retrieve relevant information

The embeddings you have generated by processing documents and data need to be stored in specific databases able to deal with their format. This database are called "Vector Store".

Vector store are not only able to store this data, bet also to perform a particular form the "similarity search" between the existing data in the database an a query we provide.

### Augment the LLM prompt

Next, the RAG agent augments your input (or prompt) by adding the relevant retrieved data in the context based on your query.

You just need to take care of the first step "Process external data", and Neuron gives you the toolkit to make it simple. The other steps are automatically managed by the Neuron RAG agent.

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

## Inspector

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls, external data sources, tools, and more. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

## Implement a RAG Agent

For RAG use cases, you must extend the `NeuronAI\RAG\RAG` class instead of the default Agent class.

To create a RAG you need to attach some additional components other than the AI provider, such as a `vector store`, and an `embeddings provider`.

Here is an example of a RAG implementation:

```php
namespace App\Neuron;

use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAIEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL'
        );
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(
            directory: __DIR__,
            name: 'demo'
        );
    }
}
```

{% hint style="warning" %}
Explore [**Data Loaders**](/v1/components/data-loader) to learn how to populate the vector store with embeddings representing the knowledge you want to integrate as additional knowledge.
{% endhint %}

### Talk to the chat bot

Imagine having previously populated the vector store with the knowledge base you want to connect to the RAG agent, and now you want to ask questions. Check out [**Data Loaders**](/v1/components/data-loader) to laern about RAG data population.

To start the execution of a RAG you call the `chat()` method:

```php
use App\Neuron\MyChatBot;
use NeuronAI\Chat\Messages\UserMessage;

$response = MyChatBot::make()->chat(
    new UserMessage('I want to know more about Inspector AI Bug Fix.')
);
    
echo $response->getContent();

// Sure, Inspector AI Bug Fix is an agentic monitoring tool 
// that provides bug fix proposals in real-time as an error occurs 
// in your application.
```

## Feed Your RAG With Documents

Once you have defined the components of your RAG system it's time to feed the vector database with embedded chunks of text.

Neuron provides you with [Data Loaders](/v1/components/data-loader) to help you set up a data loading pipeline with just a few lines of code. You can see an example below. To learn more about data loader you should check out the [dedicated documentation](/v1/components/data-loader):

```php
use App\Neuron\MyChatBot;
use NeuronAI\RAG\DataLoader\FileDataLoader;

MyChatBot::make()->addDocuments(
    // Use the file data loader component to load a text file into the vector store
    FileDataLoader::for(__DIR__.'/my-article.md')->getDocuments()
);
```

## RAG + Tools

The Neuron's RAG class extends the basic `\NeuronAI\Agent` class. This means that your RAG is always an agent and you can also attach tools and define system instructions in your implementation.

Imagine we want to implement an agent able to give workout tips based on the user data. Here is an example of a complete implementation:

```php
namespace App\Neuron;

use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;
use NeuronAI\Tools\Toolkits\Calculator\CalculatorToolkit;

class WorkoutTipsAgent extends RAG
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are an AI Agent specialized in providing workout tips."],
        );
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAIEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL'
        );
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(
            directory: __DIR__,
            name: 'demo'
        );
    }
    
    protected function tools(): array
    {
        return [
            CalculatorToolkit::make(),
        ];
    }
}
```

In the example above we created a RAG agent that is able to give workout tips to the user. We can load into the vector store the knowledge for the specific workouts you provide, so the agent has the knowledge to provide tips based on the current workout status of the user retrieved from the database with the tool we attached.


# MCP Connector

Connect your agent with Tools provided by MCP (Model Context Protocol) servers.

MCP (Model Context Protocol) is an open source standard designed by Anthropic to connect your agents to external service providers, such as your application database or external APIs.

Thanks to this protocol you can make tools exposed by an external server available to your agent.

Companies can build servers to allow developers to connect Agents to their platforms. Here are a couple of directories with most used MCP servers:

* MCP official GitHub - <https://github.com/modelcontextprotocol/servers>
* MCP-GET registry - <https://mcp-get.com/>

Once you have one or more MCP server on your machine, you can make their tools available to your agent.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    protected function provider(): AIProviderInterface
    {
        ...
    }
    
    public function instructions(): string
    {
        ...
    }
    
    protected function tools(): array
    {
        return [
            ...McpConnector::make([
                'command' => 'php',
                'args' => ['/home/code/mcp_server.php'],
            ])->tools(),
        ];
    }
}
```

You should create an `McpConnector` instance for each MCP server you want to interact to.

Neuron automatically discovers the tools exposed by the server and connects them to your agent.

When the agent decides to run a tool, Neuron will generate the appropriate request to call the tool on the MCP servers and return the result to the LLM to continue the task. It feels exactly like with your own defined tools, but you can access a huge archive of predefined actions your agent can perform with just one line of code.

### Monitoring

To stay updated about your Agent decision making process, you can connect the [Inspector monitoring dashboard](https://inspector.dev/) to monitor tool selection and execution in real-time.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

When your agent runs you will be able to explore the execution timeline in the dashboard.

<figure><img src="/files/FQ48gHvOLDpYVKbMfXEx" alt=""><figcaption></figcaption></figure>

### Filter the list of tools

During connection with complex MCP servers they can includes tools that could lead to undesired behavior in specific contexts. The `exclude()` and `only()` methods address this challenge elegantly, allowing developers to connect with comprehensive MCP servers while maintaining fine-grained control over available capabilities you want to provide to your agent.

This becomes particularly useful when working with specialized agents that need specific capabilities but you want to reduce the probability of an agent mistake, and reduce tokens consumption.

These methods accept a list of tool names that you do or do not want to associate with the agent.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    protected function provider()
    {
        return new Anthropic(...);
    }
    
    public function instructions(): string
    {
        return new SystemPrompt(["<SYSTEM PROMPT>"]);
    }
    
    protected function tools()
    {
        return [
            // EXCLUDE: discard certain tools
            ...McpConnector::make([
                'command' => 'npx',
                'args' => ['-y', '@modelcontextprotocol/server-everything'],
            ])->exclude([
                'tool_name_1',
                'tool_name_2',
                ...
            ])->tools(),
            
            // ONLY: Select the tools you want to include
            ...McpConnector::make([
                'command' => 'npx',
                'args' => ['-y', '@modelcontextprotocol/server-everything'],
            ])->only([
                'tool_name_1',
                'tool_name_2',
                ...
            ])->tools(),
        ];
    }
}
```


# Monitoring & Debugging

Monitor your AI Agents, RAGs, and Workflows in real-time.

### The Problem With AI Systems

Integrating AI Agents into your application you’re not working only with functions and deterministic code, you program your agent also influencing probability distributions. Same input ≠ output. That means reproducibility, versioning, and debugging become real problems.

Many of the Agents you build with NeuronAI will contain multiple steps with multiple invocations of LLM calls, tool usage, access to external memories, etc. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly your agent is doing and why.

Why is the model making certain decisions? What data is the model reacting to? Prompting is not programming in the common sense. No static types, small changes break output, long prompts cost latency, and no two models behave exactly the same with the same prompt.

The [Inspector](https://inspector.dev/) team designed NeuronAI with built-in observability features, so you can monitor AI agents running, helping you maintain production-grade implementations with confidence.

## Get Started With Inspector

To start monitoring your Agents you need to add the `INSPECTOR_INGESTION_KEY` variable in your application environment file. Authenticate on [app.inspector.dev](https://app.inspector.dev/register) to create a new one.

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

When your agents are being executed, you will see the details of their internal steps on the Inspector dashboard.

<figure><img src="/files/YoAtJur5JW3nLAAwE5p7" alt=""><figcaption></figcaption></figure>

If you want to monitor the whole application you can install the Inspector package based on your development environment. We provide integration packages for [PHP](https://github.com/inspector-apm/inspector-php), [Laravel](https://github.com/inspector-apm/inspector-laravel), [Symfony](https://github.com/inspector-apm/inspector-symfony), [CodeIgniter](https://github.com/inspector-apm/inspector-codeigniter), [Drupal](https://docs.inspector.dev/guides/drupal). Check out our GitHub organizations: <https://github.com/inspector-apm>.

### Create An Ingestion Key

To create an Ingestion key head to the [**Inspector dashboard**](https://app.inspector.dev/register) and create a new app.

{% hint style="success" %}
For any additional support drop in a live chat in the dashboard. We are happy to listen from your experience, find new possible improvements, and make the tool better overtime.
{% endhint %}


# Error Handling

Managing errors fired by your agent.

All exceptions fired from Neuron AI are an extension of `NeuronException` . There several type of exception that can help you understand unexpected errors, but since they inherit from the same root exception gives you the ability to precisely catch agent errors in the context of your code:

```php
try {

    // Your code here...

} catch (NeuronAI\Exceptions\NeuronException $e) {
    // catch all the exception generated just from the agent
} catch (NeuronAI\Exceptions\ProviderException $e) {
    // Fired from AI providers and embedding providers
}
```

If you want to be alerted on any error, consider to connect Inspector to your Agent instance. Learn more at the [**Observability**](/v1/advanced/observability) section.


# Asynchronous Processing

Execute multiple parallel processes using NeuronAI async interface.

NeuronAI supports asynchronous execution and parallel processing of agent requests, enabling you to efficiently handle multiple operations simultaneously. This is particularly valuable for batch processing, data classification pipelines, and high-throughput applications.

### Why Use Async Processing?

Asynchronous processing addresses several common challenges in AI-powered applications:

**Performance Optimization**: Instead of waiting for each request to complete sequentially, you can process multiple inputs simultaneously, dramatically reducing total execution time.

**Cost Efficiency**: When working with token-based pricing models, parallel processing allows you to maximize throughput within rate limits and optimize your API usage costs.

**Scalability**: Applications handling large volumes of data (product classification, content moderation, data labeling) benefit significantly from concurrent processing capabilities.

**User Experience**: In web applications, async processing prevents blocking operations that could impact response times and user experience.

**Provider Independence**: Unlike batch processing features that are provider-specific (such as OpenAI's Batch API), async processing is implemented at the framework level, making it available for all providers out of the box without relying on individual provider capabilities or implementations.

### Framework-Level vs Provider-Level Solutions

NeuronAI's async processing approach offers several advantages over provider-specific batch APIs:

**Universal Compatibility**: Async processing works with any provider supported by NeuronAI, regardless of whether they offer native batch processing capabilities.

**Consistent Interface**: You use the same async methods and patterns across all providers, eliminating the need to learn different batch implementations for each service.

**Future-Proof**: As new providers are added to NeuronAI, they automatically inherit async processing capabilities without requiring additional implementation work.

**Fallback Support**: Even if a provider discontinues or changes their batch API, your async implementation continues to work unchanged.

### Basic Async Implementation

To execute multiple agent requests in parallel, create separate agent instances for each operation and schedule the async execution using `chatAsync` method instead of the normal `chat` method. This prevents state conflicts and ensures clean execution:

```php
use GuzzleHttp\Promise\Utils;
use NeuronAI\Chat\Messages\UserMessage;

// Create separate agent instances
$agent1 = ClassificationAgent::make();
$agent2 = ClassificationAgent::make();
$agent3 = ClassificationAgent::make();

// Execute multiple parallel requests
$results = Utils::unwrap([
    'product_a' => $agent1->chatAsync(new UserMessage("Classify: Red cotton shirt, size M")),
    'product_b' => $agent2->chatAsync(new UserMessage("Classify: wireless headphones, Bluetooth 5.3")),
    'product_c' => $agent3->chatAsync(new UserMessage("Classify: laptop, Intel i7, 16GB RAM"))
]);

// Access results
echo $results['product_a']->getContent();
echo $results['product_b']->getContent();
echo $results['product_c']->getContent();
```

{% hint style="warning" %}
**Instance Isolation**: Always use separate agent instances for parallel requests. Reusing the same instance can cause state conflicts and unpredictable behavior.
{% endhint %}

### Queue-Worker Processing

For applications using message queues (RabbitMQ, Redis, SQS, etc.), async processing integrates seamlessly with worker patterns. The example below is like a pseudo-code representing a background Job to process the classification of multiple products in parallel.

You will implement your queue-worker pattern using the services provided by your framework. This is just a guideline on you can encapsulate this process:

```php
class ProductClassificationWorker
{
    public function handle(ClassificationJob $job, Inspector $inspector): void
    {
        $agents = [];
        $promises = [];
        
        // Prepare async requests
        foreach ($job->products as $id => $product) {
            $agents[$id] = ClassificationAgent::make();
            $promises[$id] = $agents[$id]->chatAsync(
                new UserMessage($product->getDescription())
            );
        }
        
        // Wait for all responses
        $results = Utils::unwrap($promises);
        
        // Process results
        foreach ($results as $id => $response) {
            // Save $response->getContent() for product ID $id
        }
    }
}
```

### Error Handling in Async Operations

When working with multiple concurrent requests, implement robust error handling to manage partial failures:

```php
use GuzzleHttp\Promise\Utils;
use NeuronAI\Exceptions\AgentException;

try {
    
    $responses = Utils::unwrap($promises);
    
} catch (AgentException $exception) {
    // Handle specific agent errors
}
```

### Performance Considerations

Asynchronous processing in NeuronAI enables you to build scalable, efficient AI-powered applications that can handle high-volume workloads while maintaining optimal performance and resource utilization.

**Memory Usage**: Each agent instance consumes memory. For very large batches, consider processing in smaller chunks to manage memory consumption.

**Rate Limits**: Be mindful of API rate limits when processing large volumes. Implement appropriate delays or throttling if needed.


# AI Provider

Interact with LLM providers or extend the framework to implement new ones.

With Neuron you can switch between LLM providers with just one line of code, without any impact on your agent implementation.

### Anthropic

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### OpenAI

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\OpenAI\OpenAI;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new OpenAI(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### AzureOpenAI

This provider allows you to connect with OpenAI models provided in the Azure cloud platform.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\AzureOpenAI;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new AzureOpenAI(
            key: 'AZURE_API_KEY',
            endpoint: 'AZURE_ENDPOINT',
            model: 'OPENAI_MODEL',
            version: 'AZURE_API_VERSION'
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### OpenAILike

This class simplify the connection with providers offering the same data format of the official OpenAI API.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\OpenAILike;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new OpenAILike(
            baseUri: 'https://api.together.xyz/v1',
            key: 'API_KEY',
            model: 'MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### Ollama

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\Ollama\Ollama;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### Gemini

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Gemini;
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new Gemini(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### Mistral

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\Mistral\Mistral;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Mistral(
            key: 'MISTRAL_API_KEY',
            model: 'MISTRAL_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### HuggingFace

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HuggingFace\HuggingFace;
use NeuronAI\Providers\HuggingFace\InferenceProvider;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new HuggingFace(
            key: 'HF_ACCESS_TOKEN',
            model: 'mistralai/Mistral-7B-Instruct-v0.3',
            // https://huggingface.co/docs/inference-providers/en/index
            inferenceProvider: InferenceProvider::HF_INFERENCE,
            parameters: [
                'max_tokens' => 500,
                'temperature' => 0.5
            ]
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### Deepseek

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Deepseek\Deepseek;
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Deepseek(
            key: 'DEEPSEEK_API_KEY',
            model: 'DEEPSEEK_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

{% hint style="danger" %}
Due to the Deepseek API limitations, it doesn't support document and image attachments.
{% endhint %}

### Grok (X-AI)

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\XAI\Grok;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new Grok(
            key: 'GROK_API_KEY',
            model: 'grok-4',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### AWS Bedrock Runtime

{% hint style="warning" %}
To use The BedrockRuntime provider you need to install the [`aws/aws-sdk-php`](https://github.com/aws/aws-sdk-php) package.
{% endhint %}

```php
namespace App\Neuron;

use Aws\BedrockRuntime\BedrockRuntimeClient;
use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\AWS\BedrockRuntime;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        $client = new BedrockAgentRuntimeClient([
            'version' => 'latest',
            'region' => 'us-east-1',
            'credentials' => [
                'key' => 'AWS_BEDROCK_KEY',
                'secret' => 'AWS_BEDROCK_SECRET',
            ],
        ]);
        
        return new BedrockRuntime(
            client: $client,
            model: 'AWS_BEDROCK_MODEL',
            inferenceConfig: []
        );
    }
}

echo MyAgent::make()->chat(new UserMessage("Hi!"));
// Hi, how can I help you today?
```

### Custom Http Options

Providers use an HTTP client to communicate with the remote service. You can customize the configuration of the HTTP client passing an instance of `\NeuronAI\Providers\HttpClientOptions`:

```php
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
            httpOptions: new HttpClientOptions(timeout: 30)
        );
    }
}
```

`HttpClientOptions` class allows customization of `timeout`, `connect_timeout`, and `headers`.

## Implement a custom provider

If you want to create a new provider you have to implement the `AIProviderInterface` interface:

```php
namespace NeuronAI\Providers;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\Tools\ToolInterface;
use NeuronAI\Providers\MessageMapperInterface;

interface AIProviderInterface
{
    /**
     * Send predefined instruction to the LLM.
     */
    public function systemPrompt(?string $prompt): AIProviderInterface;

    /**
     * Set the tools to be exposed to the LLM.
     *
     * @param array<ToolInterface> $tools
     */
    public function setTools(array $tools): AIProviderInterface;
    
    /**
     * The component responsible for mapping the NeuronAI Message to the AI provider format.
     */
    public function messageMapper(): MessageMapperInterface;

    /**
     * Send a prompt to the AI agent.
     */
    public function chat(array $messages): Message;
    
    /**
     * Yield the LLM response.
     */
    public function stream(array|string $messages, callable $executeToolsCallback): \Generator;
    
    /**
     * Schema validated response.
     */
    public function structured(string $class, Message|array $messages, int $maxRetry = 1): mixed;
}
```

The `chat` method should contains the call the underlying LLM. If the provider doesn't support tools and function calls, you can implement it with a placeholder.

This is the basic template for a new AI provider implementation.

```php
namespace App\Neuron\Providers;

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use NeuronAI\Chat\Messages\AssistantMessage;
use NeuronAI\Chat\Messages\Message;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HandleWithTools;
use NeuronAI\Providers\MessageMapperInterface;

class MyAIProvider implements AIProviderInterface
{
    use HandleWithTools;
    
    /**
     * The http client.
     *
     * @var Client
     */
    protected Client $client;

    /**
     * System instructions.
     *
     * @var string
     */
    protected string $system;

    /**
     * The component responsible for mapping the NeuronAI Message to the AI provider format.
     *
     * @var MessageMapperInterface
     */
    protected MessageMapperInterface $messageMapper;
    
    public function __construct(
        protected string $key,
        protected string $model
    ) {
        $this->client = new Client([
            'base_uri' => 'https://api.provider.com/v1',
            'headers' => [
                'Content-Type' => 'application/json',
                'Authorization' => "Bearer {$this->key}",
            ]
        ]);
    }

    /**
     * @inerhitDoc
     */
    public function systemPrompt(string $prompt): AIProviderInterface
    {
        $this->system = $prompt;
        return $this;
    }

    public function messageMapper(): MessageMapperInterface
    {
        return $this->messageMapper ?? $this->messageMapper = new MessageMapper();
    }

    /**
     * @inerhitDoc
     */
    public function chat(array $messages): Message
    {
        $result = $this->client->post('chat', [
            RequestOptions::JSON => [
                'model' => $this->model,
                'messages' => \array_map(function (Message $message) {
                    return $message->jsonSerialize();
                }, $messages)
            ]
        ])->getBody()->getContents();
        
        $result = \json_decode($result, true);

        return new AssistantMessage($result['content']);
    }
}
```

After creating your own implementation you can use it in the agent:

```php
namespace App\Neuron;

use App\Neuron\Providers\MyAIProvider;
use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    public function provider(): AIProviderInterface
    {
        return new MyAIProvider (
            key: 'PROVIDER_API_KEY',
            model: 'PROVIDER_MODEL',
        );
    }
}
```

{% hint style="warning" %}
We strongly recommend you to submit new provider implementations via PR on the official repository or using other [Inspector.dev](https://inspector.dev/developer-support/) support channels. The new implementation can receives an important boost in its advancement by the community.
{% endhint %}


# Chat History & Memory

Learn how Neuron AI manage multi turn conversations.

Neuron AI has a built-in system to manage the memory of a chat session you perform with the agent.

In many Q\&A applications you can have a back-and-forth conversation with the LLM, meaning the application needs some sort of "memory" of past questions and answers, and some logic for incorporating those into its current thinking.

For example, if you ask a follow-up question like "Can you elaborate on the second point?", this cannot be understood without the context of the previous message. Therefore we can't effectively perform retrieval with a question like this.

In the example below you can see how the Agent doesn't know my name initially:

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

$response = Agent::make()->chat(new UserMessage("What's my name?"));

echo $response->getContent();
// I'm sorry I don't know your name. Do you want to tell me more about yourself?
```

Clearly the Agent doesn't have any context about me. Now I try present me in the first message, and then ask for my name:

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

$agent = Agent::make()

$response = $agent->chat(
    new UserMessage("Hi, my name is Valerio!")
);
echo $response->getContent();
// Hi Valerio, nice to meet you, how can I help you today?


$response = $agent->chat(
    new UserMessage("Do you remember my name?")
);
echo $response->getContent();
// Sure, your name is Valerio!
```

## How Chat History works

Neuron Agents take the list of messages exchanged between your application and the LLM into an object called Chat History. It's a crucial part of the framework because the chat history needs to be managed based on the context window of the underlying LLM.

It's important to send past messages back to LLM to keep the context of the conversation, but if the list of messages grows enough to exceed the context window of the model the request will be rejected by the AI provider.

Chat history automatically truncates the list of messages to never exceed the context window avoiding unexpected errors.

## How to feed a previous conversation

Sometimes you already have a representation of user to assistant conversation and you need a way to feed the agent with previous messages.

You just need to pass an array of messages to the \`chat()\` method. This conversation will be automatically loaded into the agent memory and you can continue to iterate on it.

```php
use NeuronAI\Chat\Enums\MessageRole;
use NeuronAI\Chat\Messages\Message;

$response = MyAgent::make()
    ->chat([
        new Message(MessageRole::USER, "Hi, my company is called Inspector.dev"),
        new Message(MessageRole::ASSISTANT, "Hi, how can I assist you today?"),
        new Message(MessageRole::USER, "What's the name of the company I work for?"),
    ]);
    
echo $response->getContent();
// You work for Inspector.dev
```

The last message in the list will be considered the most recent.

## How to register a chat history

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Chat\History\InMemoryChatHistory;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        ...
    }
    
    protected function chatHistory()
    {
        return new InMemoryChatHistory(
            contextWindow: 50000
        );
    }
}
```

[`InMemoryChatHistory`](#inmemorychathistory) is used into the agent by default. Check out below to learn more

## Available Chat History Implementations

### InMemoryChatHistory

It simply store the list of messages into an array. It is kept in memory only during the current execution.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\InMemoryChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new InMemoryChatHistory(
            contextWindow: 50000
        );
    }
}
```

### FileChatHistory

This compnent makes you able to persist the ongoing conversation with the agent in a file, and resume it later in time. To create an instance of the `FileChatHistory` you need to pass the absolute path of the `directory` where you want to store conversations, and the unique `key` for the current conversation.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\FileChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new FileChatHistory(
            directory: '/home/app/storage/neuron',
            key: 'THREAD_ID',
            contextWindow: 50000
        );
    }
}
```

The `key` parameter allows you to store different files to separate conversations. You can use a unique key for each user, or the ID of a thread to make users able to store multiple conversations.

### SQLChatHistory

This component allows you to store the ongoing conversation into a SQL database. Before using this component you must create the table on your database to store messages. Here is the SQL script:

```sql
CREATE TABLE chat_history (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  thread_id VARCHAR(255) NOT NULL,
  messages LONGTEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 
  UNIQUE KEY uk_thread_id (thread_id),
  INDEX idx_thread_id (thread_id)
);
```

You can customize this table addind more columns eventually to add a relation to your users or similar use cases. You can also customize the table name passing your custom one when creating the instance.

To create an instance of the `SQLChatHistory` you need to pass the `thread_id` to separate different conversation threads, and the `PDO` connection to the database.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: 'THREAD_ID',
            pdo: new \PDO("mysql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            table: 'chat_hisotry',
            contextWindow: 50000
        );
    }
}
```

If your application is built on top of a framewrok you can easily get the PDO connection from the ORM. Here are is couple of examples in the context of Laravel or Symfony applications.

#### Laravel

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: 'CHAT_THREAD_ID',
            pdo: \DB::connection()->getPdo(),
            table: 'chat_hisotry',
            contextWindow: 50000
        );
    }
}
```

#### Symfony

You can register your agent as a service with an instance of `Doctrine\DBAL\Connection` as a constructor dependency:

```php
namespace App\Neuron;

use Doctrine\DBAL\Connection;
use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    public function __construct(protected Connection $connection)
    {}
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: 'CHAT_THREAD_ID',
            pdo: $this->connection->getNativeConnection(),
            table: 'chat_hisotry',
            contextWindow: 50000
        );
    }
}
```

## How to implement a new chat history

To create a new implementation of the chat history you must implement the `AbstractChatHistory`. It allows you to inherit several behaviors for the internal history management, so you have just to implement a couple of methods to save messages into the external storage you want to use.

```php
abstract class AbstractChatHistory implements ChatHistoryInterface
{
    public function addMessage(Message $message): ChatHistoryInterface;

    /**
     * @return Message[]
     */
    public function getMessages(): array;

    public function getLastMessage(): Message|false;

    /**
     * @param Message[] $messages
     */
    public function setMessages(array $messages): ChatHistoryInterface;

    public function flushAll(): ChatHistoryInterface;

    public function calculateTotalUsage(): int;
}
```

The abstract class already implement some utility methods to calculate tokens usage based on the AI provider responses and automatically cut the conversation based on the size of the context window. You just have to focus on the interaction with the underlying storage to add and remove messages, or clear the entire history.

We strongly suggest to look at other implementations like `FileChatHistory` to understand how to create your own.

### Serialize/Deserialize Messages

When the ChatHistory needs to store a message it must be serialized. The same way, when the ChatHistory component is instantiated it should load all the previous messages from the underlying storage (database, cache, etc) and deserialize them to the original message type.

To serialize/deserialize messages consistently the `AbstractChatHistory` provides you with `serializeMessage()` and `deserializeMessage()` methods. Here is an example of how to use them in an hypothetical database chat history implementation:

```php
<?php

namespace NeuronAI\Chat\History;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\Exceptions\ChatHistoryException;

class DatabaseChatHistory extends AbstractChatHistory
{
    public function __construct(
        protected string $db,
        protected string $key,
    ) {
        // Retrieve the current conversation from the underlying storage
        $messages = $this->db->select(...);
        
        // Deserialize properly initialize the correct message types with the correct data.
        $this->history = $this->deserializeMessages($messages);
        
        // Or deserialize messages individually
        $this->history = \array_map(
            fn(array $message) => $this->deserializeMessage($message),
            $messages
        );
    }

    protected function storeMessage(Message $message): ChatHistoryInterface
    {
        // Store the serialized version.
        $this->db->insert($message->jsonSerialize());
        return $this;
    }

    ...
}
```

The serialization/deserialization process makes messages saveable in any type of storage.


# Embeddings Provider

Integrate services to transform text into vectors for semantic search.

Transform your text into vector representations! Embeddings let you add Retrieval-Augmented Generation ([RAG](/v1/advanced/rag)) into your AI applications.

## Available Embeddings Providers

The framework already includes the following embeddings provider.

### Ollama

With Ollama you can run embedding models locally. Documentation - <https://ollama.com/blog/embedding-models>

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OllamaEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OllamaEmbeddingsProvider(
            model: 'OLLAMA_EMBEDDINGS_MODEL'
        );
    }
}
```

### Voyage AI

Documentation - <https://www.voyageai.com/>

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\VoyageEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new VoyageEmbeddingsProvider(
            key: 'VOYAGE_API_KEY',
            model: 'VOYAGE_EMBEDDINGS_MODEL' // voyage-3-large
        );
    }
}
```

### OpenAI

Documentation - <https://platform.openai.com/docs/guides/embeddings>

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAIEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_EMBEDDINGS_MODEL' // text-embedding-3-small
        );
    }
}
```

### Gemini

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\GeminiEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new GeminiEmbeddingsProvider(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_EMBEDDINGS_MODEL' // gemini-embedding-001
        );
    }
}
```

## Implement a new Provider

To create a custom provider you just have to extend the `AbstractEmbeddingsProvider` class. This class already implement the framework specific methods and let's you free to implement the only provider specific HTTP call into the `embedText()` method:

```php
namespace App\Neuron\Embeddings;

use GuzzleHttp\Client;

class CustomEmbeddingsProvider extends AbstractEmbeddingsProvider
{
    protected Client $client;

    protected string $baseUri = 'HTTP-ENDPOINT';

    public function __construct(
        protected string $key,
        protected string $model
    ) {
        $this->client = new Client([
            'base_uri' => trim($this->baseUri, '/').'/',
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer ' . $this->key,
            ]
        ]);
    }

    public function embedText(string $text): array
    {
        $response = $this->client->post('', [
            'json' => [
                'model' => $this->model,
                'input' => $text,
            ]
        ]);

        $response = \json_decode($response->getBody()->getContents(), true);

        return $response['data'][0]['embedding'];
    }
}
```

You should adjust the HTTP request based on the APIs of the custom provider.


# Vector Store

NeuronAI provides you with several ready to use interfaces against several vector databases.

We currently offer first-party support for the following vector store:

### Memory Vector Store

This is an implementation of a volatile vector store that keeps your embeddings into the machine memory for the current session. It's useful when you don't need to store the generated embeddings for long term use, but just during current interaction sessions (or for local use).

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\MemoryVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new MemoryVectorStore();
    }
}
```

### File Vector Store

File storage could be useful for low volume use case or local and staging environments. Embedded documents will be stored in the file system and processed during similarity search.

`FileVectorStore` uses PHP generators to read the embedded documents from the file systems. It will never keep more than `topK` items in memory while iterating very fast. You can store thousands of documents in your local filesystem only taking care on the maximum time you can accept to perform the similarity search.

You can also use this component to release agents with some knowledge already incorporated in a file.

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(
            directory: storage_path(),
            topK: 4
        );
    }
}
```

### Pinecone

Pinecone makes it easy to provide long-term memory for high-performance AI applications. It’s a managed, cloud-native vector database with a simple API and no infrastructure hassles. Pinecone serves fresh, filtered query results with low latency at the scale of billions of vectors.

Here is how to use Pinecone in your agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\PineconeVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new PineconeVectorStore(
            key: 'PINECONE_API_KEY',
            indexUrl: 'PINECONE_INDEX_URL'
        );
    }
}
```

Pinecone also supports hybrid search that allows you to filter documents not only by similarity with the input prompt, but also by metadata stored along with your documents. You can pass additional filters to your agent instance so Pinecone will take them in consideration while filtering documents.

You can add the `addVectorStoreFilters()` method to your agent class to pass down filters at runtime:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\PineconeVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    protected array $vectorStoreFilters = [];

    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        $store = new PineconeVectorStore(
            key: 'PINECONE_API_KEY',
            indexUrl: 'PINECONE_INDEX_URL'
        );
        
        return $store->withFilters($this->vectorStoreFilters);
    }
    
    public function addVectorStoreFilters(array $filters): self
    {
        $this->vectorStoreFilters = $filters;
        return $this;
    }
}
```

When you run your agent you can pass filters on the fly:

```php
$response = MyRAG::make()
    ->addVectorStoreFilters([
        // Add filters
    ])
    ->answer(new UserMessage(...));
```

Take a look at the Pinecone official documentation to better understand the metadata filters: <https://docs.pinecone.io/reference/api/2025-04/data-plane/query#body-filter>

### Elasticsearch

Elasticsearch's open source vector database offers an efficient way to create, store, and search vector embeddings. To use Elasticseach as a vector store in your agents implementation you have to import the official client:

```bash
composer require elasticsearch/elasticsearch
```

Here is how to create a RAG that uses Elasticsearch:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\ElasticsearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    public function __construct(protected Client $elasticClient) {}

    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new ElasticsearchVectorStore(
            client: $this->elasticClient,
            index: 'neuron-ai'
        );
    }
}
```

Passing the elasticsearch client instance to the agent:

```php
// The Inspector instance in your application - https://inspector.dev/
$inspector = new \Inspector\Inspector(
    new \Inspector\Configuration('INSPECTOR_INGESTION_KEY')
);

$elasticClient = ClientBuilder::create()
   ->setHosts(['<elasticsearch-endpoint>'])
   ->setApiKey('<api-key>')
   ->build();
   
$response = MyChatBot::make($elasticClient)
    ->observe(new AgentMonitoring($inspector))
    ->chat(new UserMessage('Hello!'));

echo $response->getContent();
```

Elasticsearch also support hybrid search. You can pass additional filters to your agent instance so Elasticsearch will take them in consideration while filtering documents.

You can add the `addVectorStoreFilters()` method to your agent class to pass down filters at runtime:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\ElasticsearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    protected array $vectorStoreFilters = [];
    
    public function __construct(protected Client $elasticClient) {}

    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        $store = new ElasticsearchVectorStore(
            client: $this->elasticClient,
            index: 'neuron-ai'
        );
    
        return $store->withFilter($this->vectorStoreFilters);
    }
    
    public function addVectorStoreFilters(array $filters): self
    {
        $this->vectorStoreFilters = $filters;
        return $this;
    }
}
```

When you run your agent you can pass filters on the fly:

```php
$response = MyRAG::make()
    ->addVectorStoreFilters([
        // Add filters
    ])
    ->answer(new UserMessage(...));
```

### Typesense

[Typesense](https://typesense.org/) is an open source alternative to the options above. To use Typesense in your agents you need to install its official client:

```bash
composer require typesense/typesense-php
```

Once you have the official client installed in your app you can return an instance of the TypesenseVectorStore in your RAG agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\TypesenseVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    public function __construct(protected Client $typesenseClient) {}

    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new TypesenseVectorStore(
            client: $this->typesenseClient,
            collection: 'neuron-ai',
            vectorDimension: 1024
        );
    }
}
```

Passing the instance of the typesense client to the Agent:

```php
// The Inspector instance in your application - https://inspector.dev/
$inspector = new \Inspector\Inspector(
    new \Inspector\Configuration('INSPECTOR_INGESTION_KEY')
);

$typesenseClient = new Client([
    'api_key' => 'TYPESENSE_API_KEY',
    'nodes' => [
        [
            'host' => 'TYPESENSE_NODE_HOST',
            'port' => 'TYPESENSE_NODE_PORT',
            'protocol' => 'TYPESENSE_NODE_PROTOCOL'
        ],
    ]
]);

$response = MyChatBot::make($typesenseClient)
    ->observe(new AgentMonitoring($inspector))
    ->chat(new UserMessage('Hello!'));

echo $response->getContent();
```

### Qdrant

[Qdrant](https://qdrant.tech/) is an open source vector database with strong similarity search capabilities. To use Qdrant in your agents you have to provide a `collectionUrl`. This means you will first need to create a collection on Qdrant with its attributes like: name, similarity search algorithm, vector dimension, etc.

Once you have the collection URL you can attach the `QdrantVectorStore` instance to your agent.

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\QdrantVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new QdrantVectorStore(
            collectionUrl: 'http://localhost:6333/collections/neuron-ai/',
            key: 'QDRANT_API_KEY'
        );
    }
}
```

### ChromaDB

[Chroma](https://trychroma.com/) is an open source database designed to be an AI application data source. To use ChromaDB in your agents you have to provide the name of an internal collection where you want to store the embeddings.

Once you have the collection created on your Chroma instance you can attach the `ChromaVectorStore` instance to the agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\ChromaVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new ChromaVectorStore(
            collection: 'neuron-ai',
            //host: 'http://localhost:8000', <-- This is by default
            topK: 5
        );
    }
}
```

### Meilisearch

[Meilisearch](https://www.meilisearch.com/) is a hybrid search engine, but the Neuron implementation uses it exclusively as a vector store for embeddings and similarity search.

Before attaching the `MeilisearchVectorStore` to your RAG agent you must create an index using the Meilisearch Admin Console and associate a custom embedder to the index configuring as Dimension the same value of the vector dimension generated by your [Embeddings Provider](/v1/components/embeddings-provider).

Once you have configured your index you can add the component to your RAG:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\MeilisearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new MeilisearchVectorStore(
            indexUid: 'MEILISEARCH_INDEXUID',
            host: 'http://localhost:8000', // Or use the cloud URL
            key: 'MEILISEARCH_API_KEY',
            embedder: 'default',
            topK: 5
        );
    }
}
```

### Implement custom Vector Stores

If you want to create a new provider you have to implement the `VectorStoreInterface` interface:

```php
namespace NeuronAI\RAG\VectorStore;

use NeuronAI\RAG\Document;

interface VectorStoreInterface
{
    public function addDocument(Document $document): void;

    /**
     * @param  Document[]  $documents
     */
    public function addDocuments(array $documents): void;
    
    public function deleteBySource(string $sourceName, string $sourceType): void;

    /**
     * Return docs most similar to the embedding.
     *
     * @param  float[]  $embedding
     * @return Document[]
     */
    public function similaritySearch(array $embedding, int $k = 4): iterable;
}
```

There are two different methods for adding a single document or a collection of documents because many databases provide different APIs for these use cases. If the database you want to interact to doesn't handle these requests differently you can implement `addDocument()` as a placeholder.

The similaritySearch should return documents with a similarity score not a similarity distance. If the underlying database returns a distance you can convert it to a score using the utility class `VectorSimilarity`:

```php
namespace App\Neuron\VectorStore;

use NeuronAI\RAG\Document;
use NeuronAI\RAG\VectorStore\VectorSimilarity;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyVectorStore implements VectorStoreInterface
{
    ...
    
    
    /**
     * @param float[] $embeddings
     */
    public function similaritySearch(array $embedding): iterable
    {
        $documents = // get documents from the vector store
        
        return \array_map(function (Document $document) {
            return $document->setScore(
                VectorSimilarity::similarityFromDistance($similarity)
            );
        }, $documents);
    }
}
```

This is the basic template for a new AI provider implementation.

```php
namespace App\Neuron\VectorStore;

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use NeuronAI\RAG\Document;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyVectorStore implements VectorStoreInterface
{
    protected Client $client;

    public function __construct(
        string $key,
        protected string $index,
        protected int $topK = 5
    ) {
        $this->client = new Client([
            'base_uri' => 'https://api.vector-store.com',
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'Authorization' => "Bearer {$key}",
            ]
        ]);
    }

    public function addDocument(Document $document): void
    {
        $this->addDocuments([$document]);
    }

    /**
     * @param Document[] $documents
     */
    public function addDocuments(array $documents): void
    {
        $this->client->post("indexes/{$this->index}", [
            RequestOptions::JSON => \array_map(function (Document $document) {
                return [
                    'vector' => $document->embedding,
                ];
            }, $documents)
        ]);
    }

    /**
     * @return Document[]
     */
    public function similaritySearch(array $embedding): iterable
    {
        // perform similarity search and return an array of Document objects
    }
}
```

After creating your own implementation you can use it in the agent:

```php
namespace App\Neuron;

use App\Neuron\VectorStore\MyVectorStore;
use NeuronAI\Agent;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyAgent extends Agent
{
    protected function vectorStore(): VectorStoreInterface
    {
        return new MyVectorStore(
            key: 'VECTORSTORE_API_KEY',
            index: 'neuron-ai',
        );
    }
}
```

{% hint style="warning" %}
We strongly recommend you to submit new vector store implementations via PR on the official repository or using other [Inspector.dev](https://inspector.dev/developer-support/) support channels. The new implementation can receives an important boost in its advancement by the community.
{% endhint %}


# Pre/Post Processor

Improve the RAG output by pre/post processing prompts and retrieval results.

As with most software systems, RAG is easy to use but hard to master. The truth is that there is more to RAG than putting documents into a vector DB and adding an LLM on top. That *can work*, but it won't always.

With RAG, you are performing a *semantic search* across many text documents — these could be tens of thousands up to tens of billions of documents.

To ensure fast search times at scale, we typically use vector search — that is, we transform our text into vectors, place them all into a vector database, and compare their proximity to a query using a similarity algorithm (like cosine similarity).

To achieve high quality responses from the RAG agent you can work on two parts of the retrieval process:

1. Optimize the user prompt (*Pre-Processors*)
2. Refine the search results gathered from the vector store (*Post-Processors*)

## Pre-Processors

Rather than treating the user's original query as the final word, the pre-processor views it as the starting point for a more sophisticated interaction with the underlying knowledge system. This isn't about second-guessing the user's intent, but about recognizing that their natural language expression often contains multiple embedded questions, implicit constraints, and contextual assumptions that need to be unpacked and reformulated to maximize retrieval effectiveness.

Consider the complexity hidden within seemingly simple queries. When someone asks "Why did our sales drop last quarter?", they're actually expressing a multi-faceted information need that might require understanding seasonal trends, competitor activities, marketing campaign effectiveness, product performance metrics, and economic indicators. A naive RAG system might retrieve general information about sales analysis, missing the opportunity to provide comprehensive, contextually relevant insights that address the full scope of the underlying question.

### Query Transformation

The core of this pattern is to use an LLM to transform the original question into a more structured prompt that the main RAG agent can use to perform a more accurate and effective document retrieval from the vector store.

Working with NeuronAI you can pass the instance of the AI provider already attached to your agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PreProcessor\QueryTransformationPreProcessor;
use NeuronAI\RAG\PreProcessor\QueryTransformationType;

class MyChatBot extends RAG
{
    ...

    protected function preProcessors(): array
    {
        return [
            new QueryTransformationPreProcessor(
                provider: $this->resolveProvider(),
                transformation: QueryTransformationType::REWRITING,
            ),
        ];
    }
}
```

Or use a different provider among the supported AI providers like Gemini, Ollama, OpenAI, HuggingFace, etc.

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PreProcessor\QueryTransformationPreProcessor;
use NeuronAI\RAG\PreProcessor\QueryTransformationType;

class MyChatBot extends RAG
{
    ...

    protected function preProcessors(): array
    {
        return [
            new QueryTransformationPreProcessor(
                // Use one of the supported AI Provider
                provider: new Anthropic(
                    key: 'ANTHROPIC_API_KEY',
                    model: 'ANTHROPIC_MODEL',
                ),
                transformation: QueryTransformationType::REWRITING,
            ),
        ];
    }
}
```

The three core strategies implemented in the NeuronAI pre-processor are: rewriting, decomposition, and HyDE (Hypothetical Document Embeddings), each tackle different aspects of this query transformation challenge.

**Query rewriting** addresses the fundamental mismatch between conversational language and search-optimized formulations. When users express their needs in casual, context-dependent language, the rewriting process translates these expressions into more precise, searchable formulations that better align with how information is typically organized and indexed.

**Decomposition** handles the reality that complex questions often contain multiple distinct information needs that would be better served by separate retrieval operations. Rather than forcing a single search to satisfy multiple different aspects of a query, decomposition breaks down complex questions into their constituent parts, allowing each component to be addressed with focused precision before synthesizing the results into a comprehensive response.

T**he HyDE approach** represents perhaps the most sophisticated strategy, working backwards from the assumption that the best way to find relevant information is to first imagine what that information might look like. Instead of searching directly with the user's question, HyDE generates hypothetical documents that would ideally answer the query, then uses these generated documents as the basis for similarity searches. This approach is particularly powerful when dealing with abstract concepts or when the user's terminology doesn't closely match the vocabulary used in the source documents.

## Post-Processors

For vector search to work instead, we need vectors. These vectors are essentially compressions of the "meaning" behind some text into (typically) 768 or 1536-dimensional vectors. There is some information loss because we're compressing this information into a single vector.

Because of this information loss, we often see that the top three (for example) vector search documents will miss relevant information. Unfortunately, the retrieval may return relevant information below our `top_k` cutoff.

What do we do if relevant information at a lower position would help our LLM formulate a better response? The easiest approach is to increase the number of documents we're returning (increase `top_k`) and pass them all to the LLM.

Unfortunately, we cannot pass everything to the LLM because this dramatically reduces the LLM's performance to find relevant information from the text placed within its context window.

The solution to this issue is retrieving plenty of documents from the vector store and then *minimizing* the number of documents that make it to the LLM. To do that, you can reorder and filter retrieved documents to keep just the most relevant for our LLM.

Neuron allows you to define a list of post-processor components to pipe as many transformations you need to optimize the agent output.

### Rerankers

Reranking is one of the most popular post-process operations you can apply to the retrieved documents. A reranking service calculates a similarity score of each documents retrieved from the vector store with the input query.

We use this score to reorder the documents by relevance and take only the most useful.

### Jina Reranker

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PostProcessor\JinaRerankerPostProcessor;
use NeuronAI\RAG\VectorStore\FileVectoreStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectoreStore(
            directory: storage_path(),
            topK: 50
        );
    }

    protected function postProcessors(): array
    {
        return [
            new JinaRerankerPostProcessor(
                key: 'JINA_API_KEY',
                model: 'JINA_MODEL',
                topN: 5
            ),
        ];
    }
}
```

In the example above you can see how the vector store is instructed to get 50 documents, and the reranker will basically take only the 5 most relevant ones.

### Cohere Reranker

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PostProcessor\CohereRerankerPostProcessor;
use NeuronAI\RAG\VectorStore\FileVectoreStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectoreStore(
            directory: storage_path(),
            topK: 50
        );
    }

    protected function postProcessors(): array
    {
        return [
            new CohereRerankerPostProcessor(
                key: 'COHERE_API_KEY',
                model: 'COHERE_MODEL',
                topN: 3
            ),
        ];
    }
}
```

### Fixed Threshold

It uses a simple, configurable fixed threshold to filter documents. Documents with scores below the threshold are removed from results.

It's ideal for scenarios requiring an explicit score cutoff for fixed quality requirements.

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\FixedThresholdPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new FixedThresholdPostProcessor(
                threshold: 0.5
            ),
        ];
    }
}
```

### Adaptive Threshold

It implements a dynamic thresholding algorithm using median and MAD (Median Absolute Deviation). It automatically adjusts to score distributions, making it robust against outliers.

You can configure a multiplier parameter that controls filtering aggressiveness.

Recommended multiplier values:

* \[0.2 to 0.4] High precision mode. For more targeted results with fewer but more relevant documents.
* \[0.5 to 0.7] Balanced mode. Recommended setting for general use cases.
* \[0.8 to 1.0] High recall mode. For more inclusive results that prioritize coverage.
* \>1.0 Not recommended as it tends to include almost all documents.

This component is ideal for cleaning up RAG results with dynamic filtering that adapts to the current result set's score distribution.

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\AdaptiveThresholdPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new AdaptiveThresholdPostProcessor(
                multiplier: 0.6
            ),
        ];
    }
}
```

## Monitoring

Neuron built-in observability features automatically trace the execution of each post processor, so you'll be able to monitor interactions with external services in your [Inspector](https://inspector.dev/) account. Learn more in the [monitoring section](/v1/advanced/observability).

<figure><img src="/files/VAoZTYWmyyPaGTPFXAEf" alt=""><figcaption></figcaption></figure>

## Extending The Framework

With Neuron you can easily create your custom post processor components by simply extending the `\NeuronAI\PostProcessor\PostProcessorInterface`:

```php
namespace NeuronAI\RAG\PostProcessor;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\RAG\Document;

interface PostProcessorInterface
{
    /**
     * Process an array of documents and return the processed documents.
     *
     * @param Message $question The question to process the documents for.
     * @param array<Document> $documents The documents to process.
     * @return array<Document> The processed documents.
     */
    public function process(Message $question, array $documents): array;
}
```

Implementing the `process` method you can perform actions on the list of documents and return the new list. Neuron will run the post processors in the same order they are listed in the `postProcessors()` method.

Here is a practical example:

```php
namespace App\Neuron\PostProcessors;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\RAG\PostProcessor\PostProcessorInterface;

// Implement your custom component
class CutOffPostProcessor implements PostProcessorInterface
{
    public function __constructor(protected int $level) {}

    public function process(Message $question, array $documents): array
    {
        /*
         * Apply a cut off on the score returned by the vector store
         */
         
        return $documents;
    }
}
```


# Data loader

Learn how to create data loader pipelines to feed your RAG applications.

{% hint style="info" %}
PREREQUISITES

This guide assumes you are already familiar with RAG. Check out the dedicated documentation: <https://docs.neuron-ai.dev/rag>
{% endhint %}

To build a structured AI application you need the ability to convert all the information you have into text, so you can generate embeddings, save them into a vector store, and then feed your Agent to answer the user's questions.

<figure><img src="/files/SM1nQe86ILby3LFV1Qrw" alt=""><figcaption></figcaption></figure>

Neuron gives you several tools (data loaders) to simplify this process.

```php
use App\Neuron\MyRAG;
use NeuronAI\RAG\DataLoader\FileDataLoader;

MyRAG::make()->addDocuments(
    // Use the file data loader component to process a text file
    FileDataLoader::for(__DIR__.'/my-article.md')->getDocuments()
);
```

Using the Neuron toolkit you can create data loading pipelines with the benefits of unified interfaces to facilitate interactions between components, like embedding providers, vector store, and file readers.

## FileDataLoader

If you need to extract text from files the `FileDataLoader` allows you to process any simple text document.

```php
use NeuronAI\RAG\DataLoader\FileDataLoader;

// Read a file and get "documents"
$documents = FileDataLoader::for(__DIR__.'/my-article.md')->getDocuments();

// Pass a directory to process all files
$documents = FileDataLoader::for(__DIR__)->getDocuments();
```

By default `FileDataLoader` read the content of a file as it is in the file system, but not all file type are ready to be treated as simple text. Neuron provides you with the ReaderInterface and several pre-defined reader components for the most common file formats.

Notice that each file reader is associated to a file extension. So based on the input file extension the data loader will automatically use the appropriate reader.

### PDF Reader

{% hint style="warning" %}
To use `PdfReader` you need to install the [**pdftotext**](https://en.wikipedia.org/wiki/Pdftotext) php extension.
{% endhint %}

```php
use NeuronAI\RAG\DataLoader\FileDataLoader;

// Register the PDF reader
$documents = FileDataLoader::for(__DIR__)
    ->addReader('pdf', new \NeuronAI\RAG\DataLoader\PdfReader())
    ->getDocuments();
```

### HML to Markdown Reader

{% hint style="warning" %}
To use `HtmlReader` you need to install the [**html2text**](https://github.com/mtibben/html2text) composer package.
{% endhint %}

```php
use NeuronAI\RAG\DataLoader\FileDataLoader;

// Register the PDF reader
$documents = FileDataLoader::for(__DIR__)
    ->addReader(['html', 'xhtml'], new \NeuronAI\RAG\DataLoader\HtmlReader())
    ->getDocuments();
```

### StringDataLoader

If you are already getting text from your database or other sources, you can use the StringDataLoader to convert this text into documents, ready to be embedded and stored by the other Neuron components in the chain:

```php
use App\Neuron\MyRAG;
use NeuronAI\RAG\DataLoader\StringDataLoader;

$contents = [
    // list of strings (text you want to embed)
];

foreach ($contents as $text) {
    $documents = StringDataLoader::for($text)->getDocuments(); 
    
    MyRAG::make()->addDocuments($documents);
}
```

### Document meta-data

After getting the array of documents from a data loader you can eventually attach custom meta-data to the document that will be saved in the vector store along with other document default fields:

```php
$documents = FileDataLoader::for($directory)->getDocuments(); 

foreach($documents as $document) {
    $document->addMetadata('user_id', 1234);
}

MyRAG::make()->addDocuments($documents);
```

Once you have these custom fields in the vector store you can use hybrid search for databases that support this feature.

{% hint style="info" %}
Hybrid search allows you to narrow the scope of a semantic search query against records that match certain criteria on other document fields rather that compare only the vector embeddings. Explore the [Vector Store section](/v1/components/vector-store) to know which database support hybrid search.
{% endhint %}

## Text Splitter

Neuron data loaders get files or text in input and generate an array of `\NeuronAI\RAG\Document` objects. These documents are embeddable units. The original text is split into smaller pieces of text to be converted into embeddings and saved in the vector store.

The logic data loaders use to split a long text into chunks can be customized using different strategies. Neuron has a dedicated component for this purpose called "Splitter", and it can be attached to the data loader based on the strategy you prefer or need:

```php
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new DelimiterTextSplitter()
    )
    ->getDocuments();
```

### DelimiterTextSplitter (default)

This is the default splitter for all data loaders.

```php
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new DelimiterTextSplitter(
            maxLength: 1000,
            separator: '.',
            wordOverlap: 0
        )
    )
    ->getDocuments();
```

Each of these parameters has an impact on the performance and accuracy of your RAG agent.

#### Max Length

Each chunk will not be longer than this value, and it will be divided into smaller documents eventually. The length can impact the accuracy of embeddings representations. The longer your units of text are, the less accurate the embeddings representation will be.

#### Separator

The text is first split into chunks based on a separator. By default the component uses the period character. You can eventually customize this separator by using any delimiter for your text.

#### Overlap

Sometimes it could be useful to bring words from the previous and next chunk into a document to increase the semantic connection between adjacent sections of the text. By default no overlap is applied.

### SentenceTextSplitter

Splits text into sentences, groups into word-based chunks, and optionally applies overlap in terms of words.

```php
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new SentenceTextSplitter(
            maxWords: 200,
            overlapWords: 0
        )
    )
    ->getDocuments();
```

**MaxWords**: maximum number of words per chunk

**OverlapWords**: number of overlapping words between chunks

### Implement Custom Splitters

You can implement a custom splitting logic implementing the `SplitterInterface`:

```php
namespace NeuronAI\RAG\Splitter;

use NeuronAI\RAG\Document;

interface SplitterInterface
{
    /**
     * @return Document[]
     */
    public function splitDocument(Document $document): array;

    /**
     * @param  Document[]  $documents
     * @return Document[]
     */
    public function splitDocuments(array $documents): array;
}
```

You can interact with external service or create your custom logic to split a long text into smaller chunks. Once you have created your custom implementation you can use it in with the data loaders:

```php
class CustomSplitter implements SplitterInterface
{
    public function splitDocument(Document $document): array
    {
        // Your logic here...
    }
    
    public function splitDocuments(array $documents): array
    {
        // Your logic here...
    }
}

// Use the custom splitter into the data loader pipeline
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new CustomSplitter()
    )
    ->getDocuments();
```

## Reindex Knowledge Source

Reindexing is a hot topic in RAG system design because the practice of breaking text into chunks makes it difficult to update individual pieces of information when the content of the original knowledge changes.

In Neuron The `Document` class is designed to carry some metadata to help you identify the source of each piece of knowledge stored into the vector database, like `sourceType` and `sourceName` fields. Using this information you can easily update the vector store with the updated version of the content from a file previously used as a source of knowledge.

{% hint style="warning" %}
The new version of the file **must have the same path and name** you used originally, otherwise the documents will be added as new ones.
{% endhint %}

```php
$documents = FileDataLoader::for("/path/to/directory")
    ->withSplitter(
        new SentenceTextSplitter(
            maxWords: 200,
            overlapWords: 0
        )
    )
    ->getDocuments();

// Reindex by sourceType and sourceName
MyRAG::make()->reindexBySource($documents);
```

If `sourceType` and `sourceName` of the Documents are already present into the vector store, they will be deleted and the Documents of the new version will be saved. Other documents will be stored as usual into the vector database.

## Use standalone components

In the examples below we used the RAG agent instance to process the final part of the ingestion pipeline: generate embeddings for document chunks, and store them into jthe vector database.

In alternative of take advantage of the RAG agent instance you can use the embedding provider and the vector store as standalone components. Remember that the vector store here must be same connected to the RAG agent.

```php
use App\Neuron\MyRAG;
use NeuronAI\RAG\DataLoader\FileDataLoader;
use NeuronAI\RAG\DataLoader\StringDataLoader;
use NeuronAI\RAG\EmbeddingProvider\OpenAIEmbeddingProvider;
use NeuronAI\RAG\VectorStore\FileVectorStore;

$embedder = new OpenAIEmbeddingProvider(
    key: 'OPENAI_API_KEY',
    model: 'OPENAI_MODEL'
);

$store = new FileVectoreStore(
    directory: __DIR__,
    key: 'demo'
);

// Process files and contents
$documents = FileDataLoader::for(__DIR__.'/documents');
    ->addReader('pdf', new \NeuronAI\RAG\DataLoader\PdfReader())
    ->getDocuments(); 

// Generate embeddings and store documents in the vector database
$store->addDocuments(
    $embedder->embedDocuments($documents)
);

```

With this simple process you can ingest GB of data into your vector store to feed your RAG agent.


# Getting Started

Guide, moderate, and control your agentic system with human-in-the-loop.

{% hint style="warning" %}
This component is in Beta stage. Its design and public APIs can change in future versions. If you want to give us your feedback post your message on the [GitHub Discussion](https://github.com/inspector-apm/neuron-ai/discussions) section
{% endhint %}

Think of a Workflow as a smart flowchart for your AI applications. The idea behind Workflow is to allow developers to use all the NeuronAI components like AI providers, embeddings, data loaders, chat history, vector store, etc, as standalone components to create totally customized agentic entities.

Agent and RAG classes represent a ready to use implementation of the most common patterns when it comes to retrieval use cases, or tool calls, structured output, etc. Workflow allows you to program your agentic system completely from scratch. Agent and RAG can be used inside a Workflow to complete tasks as any other component if you need their built-in capabilities.

What makes NeuronAI Workflows special is **interruption and human-in-the-loop** capabilities. This means your agentic system can pause mid-process, ask for human input, wait for feedback, and then continue exactly where it left off – even if that's hours or days later.

Workflow lets you create a step-by-step process where AI handles what it does best, and humans step in when judgment or oversight is needed.

Imagine you're building a content moderation system. Instead of having AI make final decisions about borderline content, your Workflow can:

1. Analyze the content using AI
2. Flag anything uncertain
3. **Pause and ask a human moderator for review**
4. Wait for the human decision
5. Continue processing based on that feedback

The key breakthrough is that **interruption isn't a bug – it's a feature**. Your Workflow remembers exactly where it stopped, what data it was working with, and what question it needs answered.

### Inspector

Before moving into the Workflow creation process, we recommend having the monitoring system in place. It could make the learning curve of how Workflow works much more easier. The best way to monitoring Workflow is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to monitoring Workflow execution:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

### Create a Workflow

A Workflow in NeuronAI is made up of two elements:

**Nodes**, with each node responsible for handling a unit of execution (manipulate data, call an agent, etc.).

**Edges**, responsible to define how the workflow must move from one node to the next. They can be conditional branches or fixed transitions.

{% hint style="success" %}
**In short**: Nodes do the work, Edges tell what to do next.
{% endhint %}

As an illustrative example, let's consider a simple workflow with two nodes. The connection (Edge) tells the workflow to go from A to B to C.

```php
<?php

namespace App\Neuron\Workflow;

use App\Neuron\Workflow\InitialNode;
use App\Neuron\Workflow\MiddleNode;
use App\Neuron\Workflow\FinishNode;
use NeuronAI\Workflow\Edge;
use NeuronAI\Workflow\Workflow;

class SimpleWorkflow extends Workflow
{
    public function nodes(): array
    {
        return [
            new InitialNode(),
            new MiddleNode(),
            new FinishNode(),
        ];
    }
    
    public function edges(): array
    {
        return [
            // Tell the workflow to go to MiddleNode after InitialNode
            new Edge(InitialNode::class, MiddleNode::class),
            
            // Tell the workflow to go to FinishNode after MiddleNode
            new Edge(MiddleNode::class, FinishNode::class),
        ];
    }
    
    protected function start(): string
    {
        return InitialNode::class;
    }
    
    protected function end(): array
    {
        return [
            FinishNode::class,
        ];
    }
}
```

<figure><img src="/files/W5eC4kZzflU0X25wOrb9" alt=""><figcaption></figcaption></figure>

### Why Use Workflows Instead of Regular Scripts?

You might be thinking: "This sounds great, but why can't I just write a regular PHP script with some if-statements and functions?" It's a fair question, and one I heard a lot while building NeuronAI. The answer becomes clear when you consider what happens when your process needs to pause, wait, and resume.

Another scenario that is practically impossible to reproduce with a procedural approach is when you need complex workflows with many branches, several loops and intermediate checkpoints, etc. When you are at the beginning and your use case is yet quite simple you couldn't see the real potential of Workflow, and it's normal. Keep in mind that if things hit the fan, NeuronAI already has a solution to help you scale.

### Development Benefits

From a developer perspective, Workflows solve several painful problems:

**Model and maintain complex iterations**: With these simple building blocks you will be able to create simple processes with a few steps, up to complex workflows with iterative loops and intermediate checkpoints.

**Human in the Loop**: Seamlessly incorporates human oversight. You can deploy AI in sensitive areas because humans are always in the loop for critical decisions.

**Debugging with inspector**: Instead of wondering why your AI made a particular decision, you can see exactly how humans and AI collaborated at each step.

**User Trust**: When users know a human reviewed important decisions, they're more likely to trust and adopt your AI system.

### Integrate with NeuronAI components ecosystem

NeuronAI Workflow lets you use individual framework components independently, giving you flexibility to integrate specific AI features into your existing workflows without the pre-packaged implementation of the `Agent` and `RAG` classes.

For example, you can include a chat history at the workflow level:

```php
$state = new WorkflowState();
$state->set('chat_history', new FileChatHistory(__DIR__, 'workflow'));

// Run the workflow passing the initial state
$workflow = new SimpleWorkflow();
$state = $workflow->run($state);
```

Inside nodes you can use the chat history methods to add and retrieve messages:

```php
class ExampleNode extends Node
{
    public function run(WorkflowState $state): WorkflowState
    {
        $message = new UserMessage(...);
        
        $state->get('chat_history')->addMessage($message);
        
        $response = $agent->chat($message);
        
        $state->get('chat_history')->addMessage($response);
        
        return $state;
    }
}
```


# Node, Edge & State

Learn how to use the fundamental elements to create your Workflow.

### Node

A Node is a simple PHP class that extends the `NeuronAI\Workflow\Node` class. The only required method to implement is `run` where you can implement the logic for the node execution.

```php
<?php

namespace App\Neuron\Workflow\Nodes;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InitialNode extends Node
{
    public function run(WorkflowState $state): WorkflowState
    {
        $answers = \array_map(fn($item) => $item['answer'], $state->get('answers'));
    
        $state->set('data', $answers);
        
        return $state;
    }
}
```

As you can see in the example above, you are not forced to run agents inside each node, but even simple PHP code. So you can create a Workflow to do practically anything! Build an agent, a RAG flow, an extraction flow, or anything else you want.

Obviously most of the times you will be interested in running Agents, but the take away here is that you are able to create processes that mix multi agent interactions and also add some data manipualtion tasks, or checkpoints in the middle.

### Workflow State

As you can see in the snippet of the Node implementation, a node gets the `WorkflowState` as input, and return the state as output. The most important role of a node is indeed to interact with the workflow state.

State is central to how Workflow operates. Each execution of the workflow creates a state that is passed between the nodes, with each node updating and reading the internal state as it executes. This process allows the workflow to maintain context and memory, critical for stateful applications.

Within Workflow, the "state" serves as a memory bank that records and tracks all the information processed by the agntic system. It’s similar to a notebook where the system captures and updates data as it moves through various stages of the workflow execution.

Whe you run the Workflow you can eventually pass an initial state with the user input for example. At first it will be injected into the start node.

```php
// Create a state with some initial data
$state = new WorkflowState();
$state->set('user_input', 'I want to know more about NeuronAI.');

// Run the workflow passing the initial state
$workflow = new SimpleWorkflow();
$state = $workflow->run($state);

// Use the final version of the state after the Workflow execution
echo $state->get('answer');
```

### Edge

Edges define the relationships and flow between nodes. They indicate how the state should be transferred from one node to another, allowing for the seamless progression of tasks. These edges essentially map the order of execution, guiding the Workflow from one task to the next. When a node completes its task, the edge determines where the result flows and which node is triggered next in the sequence.

```php
class SimpleWorkflow extends Workflow
{
    ...
    
    public function edges(): array
    {
        return [
            // Tell the workflow to go to Middle after Initial
            new Edge(InitialNode::class, MiddleNode::class),
            
            // Tell the workflow to go to Final after Middle
            new Edge(MiddleNode::class, FinalNode::class),
        ];
    }
    
    ...
}
```

### Conditional Edges

Conditional edges are specialized edges that introduce decision-making logic into the workflow. Rather than simply following a linear path, conditional edges enable agents to evaluate criteria before choosing the next node. For example, after processing some data, the agent may take one path if a condition is met (e.g., if a query returns a valid result) and a different path if the condition is not met (e.g., retrying the task). This makes workflows highly flexible, as it can dynamically adapts based on real-time information or results.

Immagine you want to implement the workflow below:

<figure><img src="/files/2kBdJHDBj3hdnj012exq" alt=""><figcaption></figcaption></figure>

It can be implemented as below with two conditional edges, one for each branch:

```php
class SimpleWorkflow extends Workflow
{
    ...
    
    public function edges(): array
    {
        return [
            new Edge(
                LLMNode::class, 
                Node1::class,
                fn(WorkflowState $state) => $state->get('accuracy') > 0.8
            ),
            
            new Edge(
                LLMNode::class, 
                Node2::class,
                fn(WorkflowState $state) => $state->get('accuracy') <= 0.8
            ),
        ];
    }
    
    protected function start(): string
    {
        return LLMNode::class;
    }
    
    protected function end(): array
    {
        return [
            Node1::class,
            Node2::class,
        ];
    }
    
    ...
}
```


# Human In The Loop

The key breakthrough is that interruption isn't a bug – it's a feature.

Neuron Workflow supports a robust **human-in-the-loop** pattern, enabling human intervention at any point in an automated process. This is especially useful in large language model (LLM)-driven applications where model output may require validation, correction, or additional context to complete the task.

Here's how it works technically:

**Interruption Points**: Any node in your Workflow can request an interruption by specifying the data it want to present to the human. This could be a simple yes/no decision, a content review, data validation, or structured data.

**State Preservation**: When an interruption happens, NeuronAI automatically saves the complete state of your Workflow. Your Workflow essentially goes to sleep, waiting for human input.

**Resume Capability**: Once a human provides the requested input, the Workflow wakes up exactly from the node it left off. No data is lost, no context is forgotten.

**External Feedback Integration**: The human input becomes part of the Workflow's data, available to all subsequent nodes. This means later steps can make better decisions based on both AI analysis and human judgment.

### Interruption

When a NeuronAI Workflow encounters an interruption, it doesn't simply stop—it preserves its entire state, and waits for guidance before proceeding. This creates a hybrid intelligence system where AI handles the computational heavy lifting while humans contribute to strategic oversight, domain expertise, and decision-making.

You can ask for an interruption calling the `interrupt()` method inside a node:

```php
<?php

namespace App\Neuron\Workflow\Nodes;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InterruptionNode extends Node
{
    public function run(WorkflowState $state): WorkflowState
    {
        // Interrupt the workflow and wait for the feedback.
        $feedback = $this->interrupt([
            'question' => 'Should we continue?',
            'current_value' => $state->get('accuracy')
        ]);
    
        if ($feedback['approved']) {
            $state->set('is_sufficient', true);
            $state->set('user_response', $feedback['response']);
            return $state;
        }
        
        $state->set('is_sufficient', false);
        return $state;
    }
}
```

Calling the `interrupt()` method you can pass the information you need to interact with the human. You will be able to catch this data later, outside of the workflow so you can inform the user with relevant information from inside the Workflow to ask for feedback.

When the Workflow will be resumed it will restart from this node, and the `$feedback` variable will receive the human's response data.

{% hint style="info" %}
**Note**: The Workflow will restart the execution from the node where it was interrupted. The node will be re-executed including the code present before the interruption.
{% endhint %}

### Manage Interruption

To be able to interrupt and resume a Workflow you need to provide a persistence component, and a workflow ID when creating the Workflow instance:

```php
$workflow = new SimpleWorkflow(
    new FilePersistence(__DIR__),
    'CUSTOM_ID'
);
```

The `ID` is the reference to save and load the state of a specific Workflow during the interruption and resume process. The interruption request from a node will fire a special type of exception represented by the `WorkflowInterrupt` class. You can catch this exception to manage the interruption request.

```php
try {
    $workflow->run();
} catch (WorkflowInterrupt $interrupt) {
    $data = $interrupt->getData();
    
    /*
     * Store $data['question'], $data['current_value'] and the Workflow-ID,
     * and alert the user to provide a feedback.
     */
}
```

Use the information in the `$data` array to guide the human in providing a feedback. Once you finally have the user's feedback you can resume the workflow. Remeber to use the same `ID` of the interrupted execution.

```php
$workflow = new SimpleWorkflow(
    new FilePersistence(__DIR__),
    'CUSTOM_ID' // <- Use the same ID of the interrupted workflow
);

// Resume the Workflow passing the human feedback
$result = $workflow->resume(['approved' => true]);

// Get the final answer
echo $result->get('answer');
```

You can take a look at the script below as an example of this process:

{% @github-files/github-code-block url="<https://github.com/inspector-apm/neuron-ai/blob/main/examples/workflow/workflow-interrupt.php>" %}


# Persistence

Store the workflow State on a persistent memory.

When we talk about persistence in NeuronAI, we're talking about the system's ability to capture and preserve the complete state of a running workflow at any moment. This includes:

* **All variables and their current values**
* **The exact execution position** – which node is active, which have completed, which are waiting
* **Context and metadata** – timestamps, user information, decision history
* **Error states and retry counters** – so failures can be handled gracefully

Think of it like a sophisticated "save game" feature, but for business processes. At any point, when an interruption is asked from a node, NeuronAI create a snapshot of your workflow's state and store it in the persistence layer. Later – whether that's seconds, hours, or weeks – the workflow can be restored to exactly that moment and continue as if nothing happened.

As usual in Neuron the Workflow persistence layer is built on top of a common interface so it's extensible and interchangeable. Below the supported persistence layer.

### When to use Persistence

Persistence comes into play when you intend to use interruption. The persistence component requires to decalre also a workflow ID.

### InMemoryPersistence

It keep data in memory only for the current execution cycle.

```php
$workflow = new SimpleWorkflow(
    new InMemoryPersistence(), 
    'CUSTOM_ID'
);
```

### FilePersistence

It will store the Workflow data and state into a local file.

```php
$workflow = new SimpleWorkflow(
    new FilePersistence(__DIR__), 
    'CUSTOM_ID'
);
```

{% hint style="warning" %}
*FilePersistence* component uses PHP serialization to store the current state of the Workflow. While this allows you to use any PHP object as an item of the Workflow state (e.g. [ChatHistory](/v1/components/chat-history-and-memory)), it also has some limitations like it does not support serialization of Closure. If objects you want to save in the Workflow state conflict with the PHP standard serialization process, you can implement the [Serializable interface](https://www.php.net/manual/en/class.serializable.php) to let the NeuronAI persistence component know of how to serialize the object in the correct way.
{% endhint %}


# Introduction

Learn what Neuron is and what you can do with it.

{% hint style="warning" %}

### Neuron v3 scheduled for the end of February.

Start with it or read the upgrade guide to update your project.&#x20;

[**Upgrade guide**](https://docs.neuron-ai.dev/neuron-v3/overview/upgrade)
{% endhint %}

### What is Neuron

Neuron is a PHP framework designed to turn your "what if" into reality. We believe that building agentic applications should be as flexible as your own logic.

By handling the heavy lifting of orchestration, data loading, and debugging, Neuron clears the path for you to focus on the creative soul of your project. From the first line of code to a fully orchestrated multi-agent system, you have the freedom to build AI entities that think and act exactly how you envision them.

We provide tools for the entire agentic application development lifecycle, from LLM interfaces, to data loading, to multi-agent orchestration, to monitoring and debugging. In addition, we provide [tutorials and other educational content](/v2/overview/fast-learning-by-video) to help you get started using AI Agents in your projects.

<figure><img src="/files/X4g0nU5EGJ0bn1dbtQcy" alt=""><figcaption><p>Neuron architecture</p></figcaption></figure>

Neuron is the perfect AI architecture for your project.

### Laravel

Neuron offers a well defined encapsulation pattern, allowing you to work on your AI components in a dedicated namespace. You can enjoy the exact same experience of the other ecosystem packages you already love, like Filament, Nova, Horizon, Pennant, etc.

<a href="https://github.com/neuron-core/laravel-travel-agent" class="button primary" data-icon="github">Example project</a>

<a href="https://www.youtube.com/watch?v=oSA1bP_j41w" class="button primary" data-icon="youtube">Watch a demo</a>

### Symfony

All Neuron components belong to its own interface, so you can easily define dependencies and automate objects creation using the Symfony service container. Watch how it works in a real project.

<a href="https://www.youtube.com/watch?v=JWRlcaGnsXw" class="button primary" data-icon="youtube">Symfony & Neuron</a>

### Developer Experience

Neuron's architecture prioritizes the fundamentals that experienced engineers expect from production-grade software.&#x20;

***

#### Strong Typing System

The framework leverages PHP 8's mature type system throughout its codebase, with every method signature, property, and return value explicitly typed. The entire framework passes PHPStan 100% type coverage.

***

#### IDE Friendly

The strongly-typed approach means your IDE can provide accurate autocompletion for agent configurations, tool parameters, and response handling. Method signatures include detailed PHPDoc annotations that provide context beyond type hints when needed, explaining parameter expectations and return value structures.

***

This foundation allows faster debugging cycles, easy integration patterns with frameworks like Symfony or Laravel. We assume you're building systems that need to be maintained, extended, and understood by teams rather than individual experiments.

#### Carefully Crafted Architecture

Whether you're working within a Laravel application, a Symfony project, a WordPress plugin, or a custom MVC framework, Neuron integrates seamlessly with your existing codebase without refactoring or disrupting established environments.

Neuron uses standard PSR interfaces where appropriate and maintains minimal external dependencies, avoiding conflits across different PHP environments and framework versions. This design choice prevents the common problem where introducing a new library increase the risks of getting stuck due to incompatible versions of dependencies.

For teams working across multiple projects, this approach provides consistency. The same Neuron patterns and implementations work regardless of whether you're building a new microservice in pure PHP, extending a WordPress site, or adding features to an enterprise Symfony and Laravel application. Knowledge transfer between projects becomes seamless, and developers can leverage their Neuron expertise across their entire PHP portfolio.

#### Community Driven

These design principles create a unified ecosystem for AI development across all PHP communities. Rather than fragmenting innovation across framework-specific solutions, Neuron enables collaboration between Laravel developers, Symfony contributors, WordPress plugin authors, and custom framework maintainers. When improvements are made to Neuron's core capabilities, they benefit every PHP developer.

Neuron's universal approach attracts contributors from across the PHP ecosystem, leading to more robust implementations, broader testing across different environments, and faster development of new features. This collaborative approach also means better support for newcomers, as experienced developers from various PHP backgrounds can provide guidance and assistance.

#### Production Readiness

Integrating AI Agents into your application you’re not working only with functions and deterministic code, you program your agent also influencing probability distributions. Same input ≠ output. That means reproducibility, versioning, and debugging become real problems.

The [Inspector](https://inspector.dev/) team designed Neuron with built-in monitoring & debugging features, so you can monitor AI agents were running, helping you deploy production-grade implementations with confidence.

Error handling and retry mechanisms are built into the framework, ensuring your agents can gracefully handle failures, rate limits, and other common issues in production environments.&#x20;

### Support For Multiple Providers

Neuron uses a common interface for large language models (`AIProviderInterface`) as well as for the other components, such as [embedding](/v2/rag/embeddings-provider), [vector stores](/v2/rag/vector-store), [toolkits](/v2/the-basics/tools#toolkits-composable-agent-capabilities), etc. The modular architecture allows you to swap components as needed, whether you're changing language model providers, adjusting memory backends, or scaling across multiple servers.

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

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
}

$message = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Ollama" %}

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Ollama\Ollama;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
        );
    }
}

$message = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="OpenAI" %}

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\OpenAI;

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

$message = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Gemini" %}

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Gemini;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Gemini(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_MODEL',
        );
    }
}

$message = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Mistral" %}

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Mistral;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Mistral(
            key: 'MISTRAL_API_KEY',
            model: 'MISTRAL_MODEL',
        );
    }
}

$message = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}
{% endtabs %}

Check out all the supported providers in the [AI Provider](/v2/the-basics/ai-provider) section.

## Create AI Agents In Laravel -  Video Tutorial

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

## What is an AI Agent

An AI agent is a software component whose output is generated by an Artificial Intelligence. These components can understand the human language and perform tasks based on the context they have and the integration tools you give to them.&#x20;

You need an agentic framework, like Neuron, to connect additional components and handle a wide range of tasks. These intelligent agents can include anything from answering simple questions to resolving complex issues that require reasoning, decision making, and proactive interactions with external systems.

Compared to a raw LLM (that primarily provide information and respond to questions within a conversation), AI agents can augment their knowledge with external sources, and take independent actions to complete tasks.

While a simple LLM can answer your questions directly during a conversation, an AI agent might be able to:

* Research information across multiple knowledge bases and compile it for you
* Manage your email by responding to simple messages
* Read data from your database and alert you via email when something important happens

The key characteristic that distinguishes agents from traditional software is their ability to operate with incomplete information and adapt to changing requirements, just like people do.

### Why Build AI Agents in PHP?&#x20;

If you're already working with PHP, Neuron allows you to integrate AI capabilities directly into your existing codebase without learning new languages or restructuring your applications. To put it simply, you don't need to learn any new programming languages. Neuron significantly reduces the barrier to entry for adding intelligence to web applications, content management systems, e-commerce platforms, and business backends.

Modern PHP offers robust object-oriented programming features, strong typing capabilities, and excellent performance characteristics that make it well-suited for AI Agents development. The language's mature ecosystem, and straightforward deployment model provide a solid foundation for building reliable agentic systems.

### The Multi-Agent Problem&#x20;

Building AI applications quickly becomes complex when multiple agents need to collaborate. Managing state between agents, handling failures gracefully,  and maintaining conversation context across different AI services creates intricate dependency chains. Without proper orchestration, developers often resort to brittle conditional logic, manual state management, and sequential processing that doesn't scale. The challenge isn't just technical—it's architectural. How do you design systems where agents can work together, share information, and recover from failures while maintaining clear, debuggable code?

<a href="/pages/LwiA5oss8HSjzdefFNvg" class="button secondary" data-icon="arrow-progress">Neuron Workflow</a>

## Ecosystem

### [E-Book - "Start With AI Agents In PHP"](https://www.amazon.it/dp/B0F1YX8KJB)

The gap between modern agentic technologies and traditional PHP development has been widening in recent years. While Python developers enjoy a wealth of libraries and frameworks to create AI Agents, PHP developers have often been left wondering how they can participate in this technological revolution without completely retooling their skillsets or rebuilding their applications from scratch.

Neuron changes all that.

This book serves as both an introduction to AI Agents concepts for developers and a comprehensive guide to Neuron framework.

<a href="https://www.amazon.com/dp/B0F1YX8KJB" class="button secondary" data-icon="amazon">Get on Amazon</a>&#x20;

<a href="https://play.google.com/store/books/details?pcampaignid=books_read_action&#x26;id=agJPEQAAQBAJ&#x26;pli=1" class="button secondary" data-icon="google">Get on GooglePlay</a>

### [Newsletter](https://neuron-ai.dev)

Register to the Neuron internal [newsletter](https://neuron-ai.dev/) to get informative papers, articles, and best practices on how to start with AI development in PHP.

You will learn how to approach AI systems in the right way, understand the most important technical concepts behind LLMs, and how to start implementing your AI solutions into your PHP application with the Neuron AI framework.

### [Forum](https://github.com/inspector-apm/neuron-ai/discussions)

We’re using [Discussions](https://github.com/inspector-apm/neuron-ai/discussions) as a place to connect with PHP developers working on Neuron to create their Agentic applications. We hope that you:

* Ask questions you’re wondering about.
* Share ideas.
* Engage with other community members.
* Welcome others and are open-minded.

### [**Inspector.dev**](https://inspector.dev)

Neuron is part of the Inspector ecosystem as a trustable platform to create reliable and scalable AI driven solutions.&#x20;

Trace and evaluate your agents execution flow to help you maintain production grade implementations with confidence. Check out the [**monitoring integrations**](/v2/the-basics/observability).

## Core components

* [**Agent**](/v2/the-basics/agent)
* [**AI Provider**](/v2/the-basics/ai-provider)
* [**Toolkit**](/v2/the-basics/tools#toolkits-composable-agent-capabilities)
* [**RAG**](/v2/rag/rag)
* [**Embeddings Provider**](/v2/rag/embeddings-provider)
* [**Data Loader**](/v2/rag/data-loader)
* [**Vector Store**](/v2/rag/vector-store)
* [**Chat History**](/v2/the-basics/chat-history-and-memory)
* [**MCP connector**](/v2/the-basics/mcp-connector)
* [**Monitoring & Debugging**](/v2/the-basics/observability)
* [**Pre/Post Processors**](/v2/rag/pre-post-processor)
* [**Workflow**](/v2/workflow/getting-started)

## Keep In Touch

* Repository: <https://github.com/inspector-apm/neuron-ai>
* Inspector: <https://inspector.dev>
* E-Book: <https://www.amazon.it/dp/B0F1YX8KJB>
* Linkedin: <https://www.linkedin.com/company/neuron-ai-php-framework>
* X: <https://x.com/neuronai_php>
* Instagram: <https://www.instagram.com/neuronai_php_adk/>
* Newsletter: [https://neuron-ai.dev](https://neuron-ai.dev/)


# Upgrade To v2 From v1

### Updating Dependencies

You should update the following dependencies in your application's `composer.json` file:

* **neuron-core/neuron-ai** to **^2.0**

### High Impact Changes

#### Workflow

The Workflow component was completely re-architected. The Edge class was completely removed and now the orchestration system is event-driven relying only on node definition.

It also supports real-time streaming.

We strongly recommend to use the V2 documentation to understand the new system and move your previously created Workflow to the new architecture.

<a href="/pages/LwiA5oss8HSjzdefFNvg" class="button secondary" data-icon="arrow-right-long">Workflow Documentation</a>

### Low Impact

#### RAG Retrieval component

In the previous version the RAG component could interact with a vector store and an embeddings provider, but there was no way to customize this behavior. Recently many different retrieval techniques emerged trying to increase the accuracy of a RAG system.

Neuron RAG now has a separate retrieval component that allwos you to implement different strategies to accomplish context retrieval from an external data source. By default RAG uses `SimilarityRetrieval` that simply replicate the previous behaviour maintaining backward compatibility. But it depends now by its own interface so you can create custom implementation and inject it into the RAG.

<a href="#rag-retrieval-component" class="button secondary" data-icon="arrow-right-long">Retrieval Documentation</a>

### New Features

#### Neuron CLI

V2 ships with practical developer experience improvements that address common friction points. The CLI tool brings the new “make” command that helps you scaffold common classes reducing boilerplate fatigue:

```bash
# Unix
php vendor/bin/neuron make:agent App\\Neuron\\MyAgent

# Windows
php .\vendor\bin\neuron make:agent App\\Neuron\\MyAgent
```

#### Evaluators

When building AI agents, evaluating their performance is crucial during this process. It's important to consider various qualitative and quantitative factors, including response quality, task completion, success, and inaccuracies or hallucinations.

Neuron introduces a system to create evaluators against test cases, so you can continues verify the output of your agentic entities overtime.

<a href="/pages/hlVUMD8XoXRBHEHgu2CP" class="button secondary" data-icon="arrow-right-long">Evaluation Documentation</a>

#### Structured Output validation rules

We introduced two new validation rules for structured output: [WordsCount](#structured-output-validation-rules) (works on string), and [InRange](#structured-output-validation-rules) (works on numeric).

#### Tool Max Tries

Agents now have a safety mechanism that tracks the number of times a tool is invoked during an execution session. If the agent exceeds this limit, execution is interrupted and an exception is thrown. By default the limit is 5 calls, and it count for each tool individually.&#x20;

You can customize this value with the `toolMaxTries()` method at agent level, or use `setMaxTries()` on the tool level. Setting Max tries on single tool takes precedence over the global setting.

```php
try {

    $result = YouTubeAgent::make()
        ->toolMaxTries(5) // Max number of calls for each tool
        ->addTool(
            // Tool level config takes precedence over the global setting
            CustomTool::make()->setMaxTries(2)
        )
        ->chat(...);
        
} catch (ToolMaxTriesException $exception) {
    // do something
}
```


# Repository Migration

We are moving Neuron to a dedicated GitHub organization to give the project its own **clear, independent identity**.&#x20;

Neuron GitHub organization: [**https://github.com/neuron-core**](https://github.com/neuron-core)

The new organization will also contain example repositories and other dedicated resources. Neuron remains **100% open source**, and this change makes it easier for the community to adopt, contribute, and grow together.

### **What you need to do (start from October 1st)**

* Open your project’s composer.json
* Find the dependency **inspector-apm/neuron-ai**
* Replace it with "**neuron-core/neuron-ai":**  "^2.0"
* Save the file and run your usual "composer update"

Here is how Neuron must be referenced in your `composer.json` file:

```json
"require": {
    ...
    "neuron-core/neuron-ai": "^2.0",
},
```

If you don’t change the package signature, you will no longer be able to receive new releases **after October 1st**.

### Stay Updated

You can receive live updates subscribing to the Neuron newsletter: [**https://neuron-ai.dev**](https://neuron-ai.dev/)


# Fast Learning by Video

Position yourself in the AI Agent era with our extensive tutorials and technical insights into Neuron capabilities. Learn from practical examples and real-world use cases.

## Video Tutorials

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

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

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

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

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

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

## Agent Development

[PHP, the Dark Horse No One Saw Coming In AI Agents development](https://inspector.dev/php-the-dark-horse-no-one-saw-coming-in-ai-agents-development/)

[LangChain alternative for PHP developers](https://inspector.dev/langchain-alternative-for-php-developers/)

[System Prompt for AI Agents In PHP](https://inspector.dev/system-prompt-for-ai-agents-in-php/)

[AI Agents Memory And Context Window In PHP](https://inspector.dev/ai-agents-memory-and-context-window-in-php/)

[Create AI Agents In PHP Powered By Google Gemini LLMs](https://inspector.dev/create-ai-agents-in-php-powered-by-google-gemini-llms/)

## RAG (Retrieval Augmented Generation)

[How to Create a RAG Agent with Neuron ADK for PHP](https://inspector.dev/how-to-create-a-rag-agent-with-neuron-adk-for-php/)

[Vector Store & AI Agents – Beyond The Traditional Data Storage](https://inspector.dev/vector-store-ai-agents-beyond-the-traditional-data-storage/)

[Improve PHP AI Agents output quality with Rerankers](https://inspector.dev/improve-php-ai-agents-output-quality-with-rerankers/)

## Tools & Toolkits

[Introducing Toolkits: Composable AI Agent Capabilities In PHP](https://inspector.dev/introducing-toolkits-composable-ai-agent-capabilities-in-php/)

[Create A Data Analyst Agent In PHP – Neuron MySQL Toolkit](https://inspector.dev/mysql-ai-toolkit-bringing-intelligence-to-your-database-layer-in-php/)

[Introducing Web Search Capabilities For PHP AI Agents](https://inspector.dev/introducing-web-search-capabilities-for-php-ai-agents/)

[Introducing Vision Capabilities for PHP AI Agents](https://inspector.dev/introducing-vision-capabilities-for-php-ai-agents/)

[AI Agents in PHP with MCP (Model Context Protocol)](https://inspector.dev/ai-agents-in-php-with-mcp-model-context-protocol/)

## Workflow

[Introducing Neuron Workflow: The future of agentic PHP applications](https://inspector.dev/introducing-neuronai-workflow-the-future-of-agentic-php-applications/)

## E-Book

The gap between modern agentic technologies and traditional PHP development has been widening in recent years. While Python developers enjoy a wealth of libraries and frameworks to create AI Agents, PHP developers have often been left wondering how they can participate in this technological revolution without completely retooling their skillsets or rebuilding their applications from scratch.

Neuron changes all that.

<figure><img src="/files/icOUu6fFXtKRtH0mEBG9" alt="" width="375"><figcaption></figcaption></figure>

As a PHP developer, you now stand at a unique intersection of technologies. For years, PHP has powered a substantial portion of the web. Now, with Neuron AI, you have the ability to infuse these web experiences with artificial intelligence, without leaving the language and ecosystem you know and love.

Neuron is the most advanced PHP framework to build AI driven applications. This book serves as both an introduction to AI Agents concepts for developers and a comprehensive guide to Neuron PHP agentic framework.&#x20;

Get it from [Amazon](https://www.amazon.com/dp/B0F1YX8KJB) or [Google Play](https://play.google.com/store/books/details?pcampaignid=books_read_action\&id=agJPEQAAQBAJ\&pli=1).

<a href="https://www.amazon.com/dp/B0F1YX8KJB" class="button secondary" data-icon="amazon">Amazon Books</a>&#x20;

<a href="https://play.google.com/store/books/details?pcampaignid=books_read_action&#x26;id=agJPEQAAQBAJ&#x26;pli=1" class="button secondary" data-icon="google">Google Play</a>


# Agentic Development

Connect the documentation to coding agents for AI Assisted Development

## Agent Skills

The [Agent Skills specification](https://agentskills.io/) is a standard for providing structured documentation to AI coding assistants. It helps AI tools understand your project's APIs, conventions, and best practices through a well-organized directory of markdown files.

Neuron publishes an Agent Skill that provides AI tools with comprehensive information about our components, including their APIs, usage patterns, interfaces, and more.

### Accessing Skills

The Agent Skill is available in the Neuron AI vendor folder at:

```bash
vendor/neuron-core/neuron-ai/skills/
        └── neuron-agent-builder/
            └── SKILL.md
        └── neuron-debugger/
            └── SKILL.md
        └── neuron-rag-specialist/
            └── SKILL.md
        └── neuron-structured-output/
            └── SKILL.md
        └── neuron-tool-creator/
            └── SKILL.md
        └── neuron-test-engineer/
            └── SKILL.md
        └── neuron-tool-creator/
            └── SKILL.md
        └── neuron-workflow-architect/
            └── SKILL.md

```

### How to install skills

How you reference the skill depends on which AI tool you're using.

#### **Claude**

If you're using [Claude Code](https://claude.ai/code), you can install the Neuron AI skills locally using the [skills CLI](https://skills.sh/):

```bash
npx skills add ./vendor/neuron-core/neuron-ai/skills
```

Once installed, the skill will be available to Claude Code automatically. The skilla are installed as a symlink, so it will automatically stay up to date when you update Neuron via composer.

#### Cursor  <a href="#cursor" id="cursor"></a>

In [Cursor](https://cursor.sh/), you can add the skill directory to your project's documentation sources via **Cursor Settings > Features > Docs**. Point it to the `vendor/neuron-core/neuron-ai/skills` .

#### Other AI Tools  <a href="#other-ai-tools" id="other-ai-tools"></a>

Most AI coding assistants that support the Agent Skills specification can use this skill. Check your tool's documentation for how to add custom skills or documentation sources.

## MCP Server

This documentation is also available and searchable as a Model Context Protocol (MCP) server. This allows AI assistants to access Neuron AI documentation content directly. The MCP server is available at: <https://docs.neuron-ai.dev/~gitbook/mcp>

### Claude Code

```
claude mcp add --transport http neuron-doc https://docs.neuron-ai.dev/~gitbook/mcp
```

### VS Code

```json
"mcp": {
    "servers": {
        "neuron-ai-doc": {
            "type": "http",
            "url": "https://docs.neuron-ai.dev/~gitbook/mcp"
        }
    }
}
```

### Cursor

```json
{
  "mcpServers": {
    "neuron-ai-doc": {
        "url": "https://docs.neuron-ai.dev/~gitbook/mcp"
    }
  }
}
```

### Windsurf

```json
{
  "mcpServers": {
    "neuron-ai-doc": {
      "serverUrl": "https://docs.neuron-ai.dev/~gitbook/mcp"
    }
  }
}
```

### OpenCode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "neuron-ai-doc": {
      "type": "remote",
      "url": "https://docs.neuron-ai.dev/~gitbook/mcp",
      "enabled": true
    }
  }
}
```


# Installation

Step by step instructions on how to install Neuron in your application and create an Agent.

{% hint style="warning" %}

### Neuron v3 scheduled for the end of February.

Start with it or read the upgrade guide to update your project.&#x20;

[**Upgrade guide**](https://docs.neuron-ai.dev/neuron-v3/overview/upgrade)
{% endhint %}

### Requirements

* PHP: ^8.1

### Install

Run the composer command below to install the latest version:

```bash
composer require neuron-core/neuron-ai
```

### Create an Agent

You can easily create your first agent with command below:

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

```bash
./vendor/bin/neuron make:agent App\\Neuron\\MyAgent
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:agent App\Neuron\MyAgent
```

{% endtab %}
{% endtabs %}

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\SystemPrompt;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }

    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are a friendly AI Agent created with Neuron framework."],
        );
    }
}
```

### Talk to the Agent

Send a prompt to the agent to get a response from the underlying LLM:

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

$response = MyAgent::make()->chat(
    new UserMessage("Hi, Who are you?")
);
    
echo $response->getContent();

// I'm a friendly AI Agent built with Neuron, how can I help you today?
```

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls, tools, external memory system, etc. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

### Video Tutorial On A Laravel Application

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


# Agent

Easily implement LLM interactions extending the basic Agent class.

You can create your agent by extending the `NeuronAI\Agent` class to inherit the main features of the framework and create fully functional agents. This class automatically manages some advanced mechanisms for you such as chat hisotry, tools and function calls, up to RAG systems. We will go into more detail about these aspects in the following sections.

Extending the base class make it easier to add custom methods and behaviour to the agent, and also promote portability, because all the moving parts are encapsulated into a single entity that you can run wherever you want in your application, or even release as a stand alone composer package.

Let's start creating an AI Agent summarizing YouTube videos. We start creating the `YouTubeAgent` class:

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

```bash
./vendor/bin/neuron make:agent App\\Neuron\\YouTubeAgent
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:agent App\Neuron\YouTubeAgent
```

{% endtab %}
{% endtabs %}

The command will create a class like this:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an instance of Anthropic, OpenAI, Gemini, Ollama, etc...
    }
    
    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are a friendly AI Agent created with Neuron framework."],
        );
    }
}
```

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

### AI Provider

The minimum implementation requires assigning an AI Provider that will be the language and reasoning engine of your agent.

The only required method to implement is `provider()`  returning the instance of the provider you want to use. Let's assume it's Anthropic.

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an instance of Anthropic, OpenAI, Gemini, Ollama, etc...
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are a friendly AI Agent created with Neuron framework."],
        );
    }
}
```

You can also use other providers like OpenAI, Gemini, or Ollama if you want to run the model locally. Check out the [supported providers](/v2/the-basics/ai-provider).

### System instructions

The second important building block is the system instructions. System instructions provide directions for making the AI ​​act according to the task we want to achieve. They are fixed instructions that will be sent to the LLM on every interaction.

That’s why they are defined by an internal method, and stay encapsulated into the agent entity. Let's implement the `instructions()` method:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider instance (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are an AI Agent specialized in writing YouTube video summaries."],
            steps: [
                "Get the url of a YouTube video, or ask the user to provide one.",
                "Use the tools you have available to retrieve the transcription of the video.",
                "Write the summary.",
            ],
            output: [
                "Write a summary in a paragraph without using lists. Use just fluent text.",
                "After the summary add a list of three sentences as the three most important take away from the video.",
            ]
        );
    }
}
```

The `SystemPrompt` class is designed to take your base instructions and build a consistent prompt for the underlying model reducing the effort for prompt engineering. The properties has the following meaning:

* **background**: Write about the role of the Agent. Think about the macro tasks it's intended to accomplish.
* **steps**: Define the way you expect the Agent to behave. Multiple steps help the Agent to act consistently.
* **output**: Define how you want the agent to respond. Be explicit on the format you expect.

We highly recommend to use the `SystemPrompt` class to increase the quality of the results, in alternative you can just return a simple string:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;

class YouTubeAgent extend Agent
{
    ...
    
    public function instructions(): string
    {
        return "You are an AI Agent specialized in writing YouTube video summaries.";
    }
}
```

### Talk to the YouTubeAgent

We are ready to test how the agent responds to our message based on the new instructions.

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

$response = YouTubeAgent::make()->chat(
    new UserMessage("Who are you?")
);
    
echo $response->getContent();
// Hi, I'm a frindly AI agent specialized in summarizing YouTube videos!
// Can you give me the URL of a YouTube video you want a quick summary of?
```

### Message

The agent always accepts input as a `Message` class, and returns Message instances.

As you saw in the example above we sent a `UserMessage` instance to the agent and it responded with an `AssistantMessage` instance. A list of assistant messages and user messages creates a chat.

We will learn more about [ChatHistory](/v2/the-basics/chat-history-and-memory) later, but it's important to know that the unified interface for the agent input and response is the Message object.

### Fluent Agent Definition

In alternative to the single class encapsulation you can also instruct the agent inline using the fluent chain of methods:

```php
$agent = Agent::make()
    ->setAiProvider(
        new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        )
    )
    ->setInstructions(
        (string) new SystemPrompt(...)
    )
    ->addTool([...]);
    
$response = $agent->chat(new UserMessage(...));
```


# AI Provider

Interact with LLM providers or extend the framework to implement new ones.

With Neuron you can switch between LLM providers with just one line of code, without any impact on your agent implementation.

### Anthropic

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### OpenAIResponses

This component uses the most recent OpenAI responses API:

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\OpenAI\Responses\OpenAIResponses;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAIResponses(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### OpenAI

This component uses the old OpenAI completions API:

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\OpenAI\OpenAI;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAI(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### AzureOpenAI

This provider allows you to connect with OpenAI models provided in the Azure cloud platform.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\AzureOpenAI;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new AzureOpenAI(
            key: 'AZURE_API_KEY',
            endpoint: 'AZURE_ENDPOINT',
            model: 'OPENAI_MODEL',
            version: 'AZURE_API_VERSION'
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### OpenAILike

This class simplify the connection with providers offering the same data format of the official OpenAI API.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\OpenAILike;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAILike(
            baseUri: 'https://api.together.xyz/v1',
            key: 'API_KEY',
            model: 'MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### Ollama

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\Ollama\Ollama;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### Gemini

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Gemini;
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Gemini(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### Gemini Vertex AI

To use this provider you need to install the `google/auth` package:

```bash
composer require google/auth
```

Below you can find the syntax to use it in your agent.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\GeminiVertex;
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new GeminiVertex(
            pathJsonCredentials: 'GOOGLE_FILE_CREDENTIALS_PATH',
            location: 'GOOGLE_LOCATION',
            projectId: 'GOOGLE_PROJECT_ID',
            model: 'GEMINI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### Mistral

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\Mistral\Mistral;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Mistral(
            key: 'MISTRAL_API_KEY',
            model: 'MISTRAL_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### HuggingFace

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HuggingFace\HuggingFace;
use NeuronAI\Providers\HuggingFace\InferenceProvider;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new HuggingFace(
            key: 'HF_ACCESS_TOKEN',
            model: 'mistralai/Mistral-7B-Instruct-v0.3',
            // https://huggingface.co/docs/inference-providers/en/index
            inferenceProvider: InferenceProvider::HF_INFERENCE,
            parameters: [
                'max_tokens' => 500,
                'temperature' => 0.5
            ]
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### Deepseek

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Deepseek\Deepseek;
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Deepseek(
            key: 'DEEPSEEK_API_KEY',
            model: 'DEEPSEEK_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### Grok (X-AI)

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HttpClientOptions;
use NeuronAI\Providers\XAI\Grok;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Grok(
            key: 'GROK_API_KEY',
            model: 'grok-4',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
            httpOptions: new HttpClientOptions(timeout: 30),
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### AWS Bedrock Runtime

To use The `BedrockRuntime` provider you need to install the [`aws/aws-sdk-php`](https://github.com/aws/aws-sdk-php) package.

```bash
composer require aws/aws-sdk-php
```

Below you can find the syntax to use it in your agent.

```php
namespace App\Neuron;

use Aws\BedrockRuntime\BedrockRuntimeClient;
use NeuronAI\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\AWS\BedrockRuntime;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        $client = new BedrockRuntimeClient([
            'version' => 'latest',
            'region' => 'us-east-1',
            'credentials' => [
                'key' => 'AWS_BEDROCK_KEY',
                'secret' => 'AWS_BEDROCK_SECRET',
            ],
        ]);
        
        return new BedrockRuntime(
            client: $client,
            model: 'AWS_BEDROCK_MODEL',
            inferenceConfig: []
        );
    }
}

$response = MyAgent::make()->chat(new UserMessage("Hi!"));
echo $response->getContent();
// Hi, how can I help you today?
```

### Custom Http Options

Providers use an HTTP client to communicate with the remote service. You can customize the configuration of the HTTP client passing an instance of `\NeuronAI\Providers\HttpClientOptions`:

```php
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
            httpOptions: new HttpClientOptions(timeout: 30)
        );
    }
}
```

`HttpClientOptions` class allows customization of `timeout`, `connect_timeout`, and `headers`.

## Implement a custom provider

If you want to create a new provider you have to implement the `AIProviderInterface` interface:

```php
namespace NeuronAI\Providers;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\Tools\ToolInterface;
use NeuronAI\Providers\MessageMapperInterface;

interface AIProviderInterface
{
    /**
     * Send predefined instruction to the LLM.
     */
    public function systemPrompt(?string $prompt): AIProviderInterface;

    /**
     * Set the tools to be exposed to the LLM.
     *
     * @param array<ToolInterface> $tools
     */
    public function setTools(array $tools): AIProviderInterface;
    
    /**
     * The component responsible for mapping the NeuronAI Message to the AI provider format.
     */
    public function messageMapper(): MessageMapperInterface;

    /**
     * Send a prompt to the AI agent.
     */
    public function chat(array $messages): Message;
    
    /**
     * Yield the LLM response.
     */
    public function stream(array|string $messages, callable $executeToolsCallback): \Generator;
    
    /**
     * Schema validated response.
     */
    public function structured(string $class, Message|array $messages, int $maxRetry = 1): mixed;
}
```

The `chat` method should contains the call the underlying LLM. If the provider doesn't support tools and function calls, you can implement it with a placeholder.

This is the basic template for a new AI provider implementation.

```php
namespace App\Neuron\Providers;

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use NeuronAI\Chat\Messages\AssistantMessage;
use NeuronAI\Chat\Messages\Message;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HandleWithTools;
use NeuronAI\Providers\MessageMapperInterface;

class MyAIProvider implements AIProviderInterface
{
    use HandleWithTools;
    
    /**
     * The http client.
     *
     * @var Client
     */
    protected Client $client;

    /**
     * System instructions.
     *
     * @var string
     */
    protected string $system;

    /**
     * The component responsible for mapping the NeuronAI Message to the AI provider format.
     *
     * @var MessageMapperInterface
     */
    protected MessageMapperInterface $messageMapper;
    
    public function __construct(
        protected string $key,
        protected string $model
    ) {
        $this->client = new Client([
            'base_uri' => 'https://api.provider.com/v1',
            'headers' => [
                'Content-Type' => 'application/json',
                'Authorization' => "Bearer {$this->key}",
            ]
        ]);
    }

    /**
     * @inerhitDoc
     */
    public function systemPrompt(string $prompt): AIProviderInterface
    {
        $this->system = $prompt;
        return $this;
    }

    public function messageMapper(): MessageMapperInterface
    {
        return $this->messageMapper ?? $this->messageMapper = new MessageMapper();
    }

    /**
     * @inerhitDoc
     */
    public function chat(array $messages): Message
    {
        $result = $this->client->post('chat', [
            RequestOptions::JSON => [
                'model' => $this->model,
                'messages' => \array_map(function (Message $message) {
                    return $message->jsonSerialize();
                }, $messages)
            ]
        ])->getBody()->getContents();
        
        $result = \json_decode($result, true);

        return new AssistantMessage($result['content']);
    }
}
```

After creating your own implementation you can use it in the agent:

```php
namespace App\Neuron;

use App\Neuron\Providers\MyAIProvider;
use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new MyAIProvider (
            key: 'PROVIDER_API_KEY',
            model: 'PROVIDER_MODEL',
        );
    }
}
```

{% hint style="warning" %}
We strongly recommend you to submit new provider implementations via PR on the official repository or using other [Inspector.dev](https://inspector.dev/developer-support/) support channels. The new implementation can receives an important boost in its advancement by the community.
{% endhint %}


# Tools & Toolkits

Give Agents the ability to interact with your application context and services.

The core agent loop involves calling a model, letting it choose tools to execute, and then finishing when no more tools are needed to provide a response:

<figure><img src="/files/Q1cuAmBYD1rfK2Gqh7uO" alt=""><figcaption></figcaption></figure>

### What is a Tool

Tools enable Agents to go beyond generating text by facilitating interaction with your application services, or external APIs.

Think about Tools as special functions that your AI agent can use when it needs to perform specific tasks. They let you extend your Agent's capabilities by giving it access to specific functions it can call inside your code.

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

In the [YouTubeAgent](/v2/the-basics/agent) example we can define a tool to make the Agent able to retrieve the YouTube video transcription, so it can crteate a short summary:

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\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
    {
        // return an AI provider instance (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string 
    {
        return (string) new SystemPrompt(
            background: ["You are an AI Agent specialized in writing YouTube video summaries."],
            steps: [
                "Get the url of a YouTube video, or ask the user to provide one.",
                "Use the tools you have available to retrieve the transcription of the video.",
                "Write the summary.",
            ],
            output: [
                "Write a summary in a paragraph without using lists. Use just fluent text.",
                "After the summary add a list of three sentences as the three most important take away from the video.",
            ]
        );
    }
    
    protected function tools(): array
    {
        return [
            Tool::make(
                'get_transcription',
                'Retrieve the transcription of a youtube video.',
            )->addProperty(
                new ToolProperty(
                    name: 'video_url',
                    type: PropertyType::STRING,
                    description: 'The URL of the YouTube video.',
                    required: true
                )
            )->setCallable(function (string $video_url) {
                return "Video transcripton...";
            })
        ];
    }
}

```

Let’s break down the code.

We introduced the new method `tools()` into the Agent class. This method expects to return an array of Tool objects that the AI will be able to use if needed.

In this example we return an array of just one tool, named `get_transcription`.

Notice that the `ToolProperty` we define should match with the signature of the function you use as a callable. The callable gets the `$video_url` arguments, and the name of the property is exactly "video\_url".

The most important thing are the name and description you give to the tool and its properties. All these pieces of information will be passed to the LLM in natural language. The more explicit and clear you are, the more likely the LLM understands when, if, and why, it’s the case to use the tool.

Once the Agent decides to use a tool the callable function is executed. Here we can implement the logic to retrieve the video transcription and return the information back to the LLM.

Neuron provides you with these clear and simple APIs and automates all the underlying interactions with the LLM. Once you get the point it can immediately open to a possibility to connect basically everything you want to the Agent. Being able to execute local functions allows you to invoke any external APIs or application components.

### Custom Tools

Thanks to the Neuron modular architecture, Tools are components that implement `ToolInterface` . You are free to create pre-packaged tool classes to make the agent able to perform sapecific actions, and release them as external composer packages or submit a PR to our repository to have them integrated into the core framework.

To create a new Tool execute the console command below:

{% 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 %}

You can customize the scaffolding of the tool with the code below:

```php
<?php

namespace App\Neuron\Tools;

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

class GetTranscriptionTool extends Tool
{
    protected Client $client;
    
    public function __construct(protected string $key)
    {
        // Define Tool name and description
        parent::__construct(
            'get_transcription',
            'Retrieve the transcription of a youtube video.',
        );
    }
    
    /**
     * Return the list of properties.
     */
    protected function properties(): array
    {
        return [
            new ToolProperty(
                name: 'video_url',
                type: PropertyType::STRING,
                description: 'The URL of the YouTube video.',
                required: true
            )
        ];
    }
    
    /**
     * Implementing the tool logic
     */
    public function __invoke(string $video_url): string
    {
        $response = $this->getClient()
            ->get('transcript?url=' . $video_url.'&text=true')
            ->getBody()
            ->getContents();

        $response = json_decode($response, true);

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

**Tool name and description**: Define name and description of the tool in the tool constructor. Invest in prompt engineering to help the model take better decisions.

**The properties method**: Implement this method to return the list of properties the tool expects.

**The `__invoke` method**: Here you need to implement the logic of the tool, and return a result that will be returned back to the model. The PHP `__invoke` magic method is used by default.

Notice how the `__invoke()` method accepts the same arguments defined by the `ToolProperty` . In this example I'm using an external service to retrieve the YouTube video transcription called [Supadata.ai](https://supadata.ai/).

You can attach the tool in the agent class as usual:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\SystemPrompt;
use App\Neuron\Tools\MyCustomTool;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider instance (Gemini, OpenAI, Ollama, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string
    {
        return (string) new SystemPrompt(...);
    }
    
    protected function tools(): array
    {
        return [
            GetTranscriptionTool::make('API_KEY'),
        ];
    }
}
```

GetTranscriptions is just an example. You can eventually implement other tools to make the Agent able to retrieve other video metadata to enhance its video analysis capabilities.

Finally you can talk to the agent asking for the summary of a YouTube video.

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

$response = YouTubeAgent::make($user)->chat(
    new UserMessage('What about this video: https://www.youtube.com/watch?v=WmVLcj-XKnM')
);
    
echo $response->getContent();

/**

Based on the transcription, I'll provide a summary of this powerful environmental 
message from "Mother Nature":
This video presents ...

Three most important takeaways:

1. Nature has existed ...

2. The wellbeing of humanity is ...

3. How humans choose to act toward Nature determines ...

*/
```

### Max Tries

Agents now have a safety mechanism that tracks the number of times a tool is invoked during an execution session. If the agent exceeds this limit, execution is interrupted and an exception is thrown. By default the limit is 5 calls, and it count for each tool individually.

You can customize this value with the `toolMaxTries()` method at agent level, or use `setMaxTries()` on the tool level. Setting max tries on single tool takes precedence over the global setting.

```php
try {

    $result = YouTubeAgent::make()
        ->toolMaxTries(5) // Max number of calls for each tool
        ->addTool(
            // Tool level config takes precedence over the global setting
            CustomTool::make()->setMaxTries(2)
        )
        ->chat(...);
        
} catch (ToolMaxTriesException $exception) {
    // do something
}
```

### Visibility

You can condition the visibility of tools based on custom rules. The Tool class provides you with the `visible` method to determine if the agent should even known this tool exists:

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

If the `visible` method get `false`, the tool will not be available during agent execution.

### Monitoring & Debugging

Neuron automatically manages the tool loop for you, based on what the LLM decided to call.

To watch inside this workflow you should connect your Agent to the [Inspector monitoring dashboard](https://inspector.dev/) in order to see the tool call execution flow in real-time.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

<figure><img src="/files/1G08C77SsiYbNdVXyz2o" alt=""><figcaption></figcaption></figure>

In the image below you can see all the details about the execution of the tool to retrieve the transcription of the video:

<figure><img src="/files/VsBcEyMZwrEe97bQng3i" alt=""><figcaption></figcaption></figure>

## Tool Properties

Neuron allows you to define the format of the data you want to receive into the tool function. You can nest these objects inside each other to define complex data structures.

### ToolProperty

This class represent a simple scalar value like string, int, or boolean.

```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: 'Describe the value you expect',
                required: true
            )
        ];
    }
    
    public function __invoke(string $arg){...}
}
```

### ArrayProperty

The `ArrayProperty` allows you to require a list of items with specific characteristics.

Use the argument `items` to specify the data type of the array elements. In the example below we ask for an array of string.

```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: 'Describe the value you expect',
                required: true,
                items: new ToolProperty(
                    name: 'prop',
                    type: PropertyType::STRING,
                    description: 'Describe the value you expect',
                    required: true
                )
            )
        ];
    }
    
    public function __invoke(string $arg){...}
}
```

#### Max and Min limits

The ArrayProperty allows you also to define limitations about the size of the expected array using `minItems` and `maxItems` arguments.

```php
$property = new ArrayProperty(
    name: "tags",
    description: "List of tags associated with the item",
    required: true,
    items: new ToolProperty(
        name: "tag",
        type: PropertyType::STRING,
        description: "A single tag",
        required: true
    ),
    minItems: 1,
    maxItems: 10
);
```

### ObjectProperty

Similar to the array example above you can define an object data structure:

```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 color',
                required: true,
                properties: [
                    new ToolProperty(
                        name: 'r',
                        type: PropertyType::NUMBER,
                        description: 'The red part of the RGB',
                        required: true
                    ),
                    new ToolProperty(
                        name: 'g',
                        type: PropertyType::NUMBER,
                        description: 'The green part of the RGB',
                        required: true
                    ),
                    new ToolProperty(
                        name: 'b',
                        type: PropertyType::NUMBER,
                        description: 'The blue part of the RGB',
                        required: true
                    )
                ]
            )
        ];
    }
    
    public function __invoke(string $arg){...}
}
```

### Structured Tool Input

If the obect you want has many properties you can pass a structured PHP class to the `ObjectProperty` instead of defining the schema manually. Neuron will provide you with an instance of this class as the input argument of the tool function:

```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: 'Combination of colors',
                required: true,
                class: Color::class
            )
        ];
    }
    
    public function __invoke(Color $color){...}
}
```

Here is how the Colors class looks like:

```php
<?php

namespace App\Neuron\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;

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

## Provider Tools

Some providers offer the possibility to use their built-in tools like web\_search, file\_search, and others instead of relying on external services. Even they offer this service they introduce a lot of constraints using these tools. The most flexible and reliable way to add cpabailities to your agents remains the Tools and Toolkit systems.

You can add a provider tool as usual in the tools array of your agent:

```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([...]),
        ];
    }
}
```

Currently only [OpenAIResponses](/v2/the-basics/ai-provider#openairesponses), [Gemini](/v2/the-basics/ai-provider#gemini), and [Anthropic](/v2/the-basics/ai-provider#anthropic) support these tools.

## Toolkits

The philosophy behind Neuron's toolkit system emerged from a fundamental observation during AI Agent Development: while individual tools provide specific capabilities, real-world AI agents often require coordinated sets of related functionalities.

Rather than forcing developers to manually assemble collections of tools for common use cases, Neuron introduces toolkits as an abstraction layer that transforms how we think about agent capability composition.

The traditional approach requires instantiating each tool individually. Imagine you want to build agents that need mathematical reasoning – addition, subtraction, multiplication, division, and exponentiation tools must all be declared separately in the agent's tool configuration. This granular approach quickly becomes unwieldy when agents require comprehensive functionality sets.

Toolkits represent Neuron's solution to this complexity, packaging tools created around the same scope into a single, coherent interface that can be attached to any agent with a single line of code.

Here is an example of the `CalculatorToolkit`:

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

use NeuronAI\Tools\Toolkits\AbstractToolkit;

class CalculatorToolkit extends AbstractToolkit
{
    public function guidelines(): ?string
    {
        return "This toolkit allows you to perform mathematical operations. You can also use this functions to solve
        mathematical expressions executing smaller operations step by step to calculate the final result.";
    }

    public function provide(): array
    {
        return [
            SumTool::make(),
            SubtractTool::make(),
            MultiplyTool::make(),
            DivideTool::make(),
            ExponentiateTool::make(),
        ];
    }
}
```

The `AbstractToolkit` base class establishes a consistent interface that all toolkits inherit, ensuring predictable behavior across the framework.

#### Guidelines

The `guidelines()` method serves a particularly important function in agent development – it provides contextual information that helps the underlying language model understand not just what tools are available, but how they should be used together. In the case of the `CalculatorToolkit`, the guidelines explicitly suggest that complex mathematical expressions can be solved through step-by-step operations, guiding the agent toward effective problem-solving strategies.

#### Provide Tools

The `provide()` method returns the array of tools included in the toolkit by default. When a toolkit is attached to an agent, the individual tools become available exactly as if they had been added separately, but without the cognitive overhead of managing multiple tool declarations. Here is how you can add it to your 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(),
        ];
    }
}
```

### Filters

During development of complex agents, I've frequently encountered scenarios where a toolkit provides mostly the right functionality but includes tools that could lead to undesired behavior in specific contexts, or just need to be restricted and configured individually.

#### Exclude

The `exclude()` method addresses this challenge elegantly, allowing developers to attach comprehensive toolkits while maintaining fine-grained control over available capabilities. This becomes particularly useful when working with specialized agents that need specific capabilities but you want to reduce the probability of an agent mistake, and reduce tokens consumption.

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

The exclusion mechanism operates at the class level, using fully qualified class names to identify tools for removal.

#### Only

In the same way you can also use the method `only()` to request a sub-set of the available tools in the toolkit.

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

#### With

Following the same pattern you may need to retrieve an instance of a specific tool from the toolkit to change its settings. You can do this using the `with()` method. You can pass the fully qualified class name to declare what tool you want to retrieve, and the tool instance will be injected into the callback so you can change its settings and return it back.

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

From an extensibility perspective, the toolkit system opens remarkable opportunities for community contribution and ecosystem growth. The consistent interface means that third-party developers can create domain-specific toolkits that integrate seamlessly with Neuron's architecture. A developer building agents for financial applications might create a FinancialToolkit that includes tools for currency conversion, interest calculation, and risk assessment. Similarly, a WebScrapingToolkit could package HTTP request tools, HTML parsing capabilities, and data extraction utilities into a single, reusable component.

## Available Toolkits

Neuron ships with several built-in tools and toolkits that allows you to quickly equip your agents with many skills. You can use these tools individually or attach entire toolkits with a single line of code.

### Calculator

The CalculatorToolkit provides a comprehensive suite of computational tools designed to make your AI agents performs accurate calculations. It can seamlessly integrates with complementary toolkits that provide data access—such as database connectors, CSV processors, API clients, or spreadsheet readers—enabling AI agents to perform sophisticated statistical calculations, and deliver comprehensive insights in response to complex business queries.

```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>sum</td><td>NeuronAI\Tools\Toolkits\Calculator\SumTool</td></tr><tr><td>subtract</td><td>NeuronAI\Tools\Toolkits\Calculator\SubtractTool</td></tr><tr><td>multiply</td><td>NeuronAI\Tools\Toolkits\Calculator\MultiplyTool</td></tr><tr><td>divide</td><td>NeuronAI\Tools\Toolkits\Calculator\DivideTool</td></tr><tr><td>exponential</td><td>NeuronAI\Tools\Toolkits\Calculator\ExponentialTool</td></tr><tr><td>square root</td><td>NeuronAI\Tools\Toolkits\Calculator\SquareRootTool</td></tr><tr><td>nth root</td><td>NeuronAI\Tools\Toolkits\Calculator\NthRootTool</td></tr><tr><td>mean</td><td>NeuronAI\Tools\Toolkits\Calculator\MeanTool</td></tr><tr><td>median</td><td>NeuronAI\Tools\Toolkits\Calculator\MedianTool</td></tr><tr><td>mode</td><td>NeuronAI\Tools\Toolkits\Calculator\ModeTool</td></tr><tr><td>standard deviation</td><td>NeuronAI\Tools\Toolkits\Calculator\StandardDeviationTool</td></tr><tr><td>variance</td><td>NeuronAI\Tools\Toolkits\Calculator\VarianceTool</td></tr></tbody></table>

### Calendar

​This toolkit provides comprehensive date and time operations. Use these tools to make your agent able to work with dates, times, formatting, calculations, and timezone conversions.

```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><thead><tr><th width="205"></th><th></th></tr></thead><tbody><tr><td>current_datetime</td><td>NeuronAI\Tools\Toolkits\Calendar\CurrentDateTimeTool</td></tr><tr><td>get_timestamp</td><td>NeuronAI\Tools\Toolkits\Calendar\GetTimestampTool</td></tr><tr><td>format_date</td><td>NeuronAI\Tools\Toolkits\Calendar\FormatDateTool</td></tr><tr><td>date_difference</td><td>NeuronAI\Tools\Toolkits\Calendar\DateDifferenceTool</td></tr><tr><td>add_time</td><td>NeuronAI\Tools\Toolkits\Calendar\AddTimeTool</td></tr><tr><td>subtract_time</td><td>NeuronAI\Tools\Toolkits\Calendar\SubtractTimeTool</td></tr><tr><td>calculate_age</td><td>NeuronAI\Tools\Toolkits\Calendar\CalculateAgeTool</td></tr><tr><td>convert_timezone</td><td>NeuronAI\Tools\Toolkits\Calendar\ConvertTimezoneTool</td></tr><tr><td>get_timezone_info</td><td>NeuronAI\Tools\Toolkits\Calendar\GetTimezoneInfoTool</td></tr><tr><td>get_weekday</td><td>NeuronAI\Tools\Toolkits\Calendar\GetWeekdayTool</td></tr><tr><td>is_weekend</td><td>NeuronAI\Tools\Toolkits\Calendar\IsWeekendTool</td></tr><tr><td>is_leap_year</td><td>NeuronAI\Tools\Toolkits\Calendar\IsLeapYearTool</td></tr><tr><td>get_days_in_month</td><td>NeuronAI\Tools\Toolkits\Calendar\GetDaysInMonthTool</td></tr><tr><td>start_of_period</td><td>NeuronAI\Tools\Toolkits\Calendar\StartOfPeriodTool</td></tr><tr><td>end_of_period</td><td>NeuronAI\Tools\Toolkits\Calendar\EndOfPeriodTool</td></tr><tr><td>get_week_number</td><td>NeuronAI\Tools\Toolkits\Calendar\GetWeekNumberTool</td></tr><tr><td>compare_dates</td><td>NeuronAI\Tools\Toolkits\Calendar\CompareDatesTool</td></tr><tr><td>is_date_in_range</td><td>NeuronAI\Tools\Toolkits\Calendar\IsDateInRangeTool</td></tr></tbody></table>

### MySQL & PostgreSQL

These toolkits make your agent able to interact with your database. If you ask "How many votes did the authors get in the last 14 days?", the agent doesn’t guess or hallucinate an answer. Instead, it recognizes that this question requires database access, identifies the appropriate tables involved and retrieves real data from your system.

<figure><img src="/files/QXqLeOpSQGxT99N8W64v" alt=""><figcaption></figcaption></figure>

All the tools in the MySQL and PostgreSQL toolkits require a [PDO](https://www.php.net/manual/en/class.pdo.php) instance as a constructor argument. If you are in a framework environment or you are already using an ORM in general, you can gather the underlying PDO instance from the ORM and pass it to the tools. You can learn more about this implementation strategy in this in-depth article: <https://inspector.dev/mysql-ai-toolkit-bringing-intelligence-to-your-database-layer-in-php/>

The PDO instance is basically a connection to a specific database, so you could aslo think to create dedicated credentials for your agent. It could be helpful to control the level of access your agent has to the database.

Anyway you have separate tools for reading and writing to the database. If you are not confident about your agent behaviour you may not provide the writing tool.

```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 [
            // Connect to a MySQL database
            MySQLToolkit::make(
                new \PDO("mysql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            ),
            
            // or Postgre database
            PGSQLToolkit::make(
                new \PDO("pgsql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            ),
        ];
    }
}
```

{% hint style="warning" %}
These examples refer to the `MySQLToolkit` but it's exactly the same using `PGSQLToolkit`.
{% endhint %}

#### MySQLSchemaTool / PGSQLSchemaTool

This tool allows agents to understand the structure of your database, enabling them to construct intelligent queries without requiring you to hardcode table structures or relationships into prompts. This tool essentially gives your agent the equivalent of a database administrator’s understanding of your schema, allowing it to craft queries that respect your data model and take advantage of existing indexes and relationships.

```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(...)),
        ];
    }
}
```

This tool also accept a second argument `$tables`. You can basically pass a list of tables that you want to include in the schema information passed to the LLM. This is basically a way to limit the scope of the queries the agent will later execute on the database.

```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']
            ),
        ];
    }
}
```

By limiting the schema scope, you can create specialized agents that focus on specific areas of your application. A content management agent might only need access to articles, categories, and tags, while a user administration agent requires visibility into users, roles, and permissions tables. This approach not only improves performance but also reduces the cognitive load on the language model, leading to more accurate and focused responses.

#### MySQLSelectTool / PGSQLSelectTool

Use this tool to make your agent able to run SELECT query against the database.

```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

Use this tool to make your agent able to performs write operations against the database (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(...)),
        ];
    }
}
```

### Tavily

This toolkit enable your agent to performs web search, page content extraction, and crawling.

```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 Web Search

It makes your Agent able to search the web. It requires access to [Tavily APIs](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'
            ),
        ];
    }
}
```

You can customize the default options to retrieve search results by passing your preference in the `withOptions` method:

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

#### Tavily Extract

Extract web page content from an URL. It requires access to [Tavily APIs](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 Crawl

Tavily Crawl is a graph-based website traversal tool that can explore hundreds of paths in parallel with built-in extraction and intelligent discovery.

```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

This toolkit enable your agent to performs web search, and read the content of a specific 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 Web Search

It makes your Agent able to search the web. It requires access to [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 Reader

Extract web page content from an URL. It requires access to [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'
            ),
        ];
    }
}
```

### Zep Memory

This toolkit connects a NeuronAI Agent to [Zep](https://www.getzep.com/) knowledge graph. This kind of system allows the agent to store relevant facts that may emerge during interactions with the agent over time. It's a long term memory in the sense that is not limited to the current conversation like the [ChatHistory](/v2/the-basics/chat-history-and-memory) component does. It's an external persistent storage the agent will use to store and retrieve single pieces of information that can allow more personalized answers.

To learn more about the capabilities of these kind of system you can visit the Zep website: <https://www.getzep.com/>

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Zep\ZepLongTermMemoryToolkit;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            ZepLongTermMemoryToolkit::make(
                key: 'ZEP_API_KEY',
                user_id: 'ID'
            ),
        ];
    }
}
```

The `user_id` arguments allows you to separate the long term memory in different silos if you want to serve multiple users. Based on your use case you can use this parameter as a "key" to separate the memory for the various entities the agent interact to (users, companies, etc.).

### AWS SES

#### Simple Email Service (SES)

This tool allows the agent to send an email message to one or more recipients, send notifications, confirmations, reports, or any other email-based communication. The tool handles proper email delivery, and basic error handling automatically.

In order ti use this tool the AWS sdk for PHP must be installed.

```
composer require aws/aws-sdk-php
```

The tool gets an instance of the `SesClient` class from the AWS PHP sdk.

```php
namespace App\Neuron;

use Aws\Ses\SesClient;
use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\AWS\SESTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            SESTool::make(
                sesClient: new SesCleint(...),
                fromEmail: 'my-address@email.com'
            ),
        ];
    }
}
```

### Supadata YouTube

This toolkit provides access to YouTube video transcriptions, metadata, channel information,\
and playlist data through Supadata.ai for content analysis and research purposes.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataYouTubeToolkit;

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

#### Video Transcription

Allow the agent to retrieve the transcription of a youtube video.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataVideoTranscriptTool;

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

#### Video Metadata

Allow the agent to retrieve the metadata of a youtube video.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataVideoMetadataTool;

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

#### Channel Metadata

Allow the agent to retrieve metadata from a YouTube channel including name, description, subscriber count, and more.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataYoutubeChannelTool;

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

#### Playlist Metadata

Allow the agent to retrieve metadata from a YouTube playlist including title, description, video count, and more.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataYoutubePlaylistTool;

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

## Parallel Tool Calls

If your agents are tool-hungry, you can enable parallel execution if the model ask for multiple tool calls in a single request.

#### Sequential Execution (Standard)

The agent calls tools **one at a time**, waiting for each to complete before starting the next:

```
1. Call tool A → wait for result
2. Call tool B → wait for result  
3. Call tool C → wait for result

Total time: Time(A) + Time(B) + Time(C)
```

#### Parallel Execution (With `pcntl`)

The agent calls **multiple tools simultaneously**, letting them run at the same time:

```
1. Call tool A, B, and C all at once
2. Wait for all to complete

Total time: Max(Time(A), Time(B), Time(C))
```

### Enable parallel execution

Just attach the `ParallelToolCalls` trait to your Agent or RAG agent:

```php
use NeuronAI\Tools\ParallelToolCalls;

class DemoAgent extends Agent
{
    use ParallelToolCalls;

    protected function provider(): AIProviderInterface
    {
        ...
    }

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

### Requirements

To use this trait you need to install the `spatie/fork` package. For more information check out the GitHub repository: <https://github.com/spatie/fork>

```
composer require spatie/fork
```

{% hint style="warning" %}
**Limitations**

This implementation requires the `pcntl` extension which is installed in many Unix and Mac systems by default.

**pcntl only works in CLI processes, not in a web context.**

If the `pcntl` extension is not present in the system running the agent (e.g. Windows machines) the trait automatically fallbacks to the standard tool calls execution. This can be helpful if you have a missmatch between your local development environment and the production environment. You can develop locally with `pcntl` disabled, then deploy to production environments where it may be enabled—**without modifying a single line of code**. The agent adapts automatically to whatever execution environment it finds itself in.
{% endhint %}


# MCP

Connect the tools provided by Model Context Protocol (MCP) servers to your agent.

MCP (Model Context Protocol) is an open source standard designed by Anthropic to connect your agents to external service providers, such as your application database or external APIs.

Thanks to this protocol you can make tools exposed by an external server available to your agent.

Companies can build MCP servers to allow developers to connect Agents to their platforms. Here are a couple of directories with most used MCP servers:

* MCP official GitHub - <https://github.com/modelcontextprotocol/servers>
* MCP-GET registry - <https://mcp-get.com/>

### How it works

Neuron provides you with the `McpConnector` class that you can instantiate passing the MCP server configuration.

```php
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    ...
    
    protected function tools(): array
    {
        return [
            ...McpConnector::make([
                'command' => 'php',
                'args' => ['/home/code/mcp_server.php'],
            ])->tools(),
        ];
    }
}
```

You should create an `McpConnector` instance for each MCP server you want to interact to.&#x20;

Neuron automatically discovers the tools exposed by the server and connects them to your agent.

When the agent decides to run a tool, Neuron will generate the appropriate request to call the tool on the MCP servers and return the result to the LLM to continue the task.  It feels exactly like with your own defined tools, but you can access a huge archive of predefined actions your agent can perform with just one line of code.

### Local MCP Server

If you want to connect with an MCP server installed locally on your machine or VM, you can use the "command" style configuration.

```php
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    ...
    
    protected function tools(): array
    {
        return [
            ...McpConnector::make([
                'command' => 'php',
                'args' => ['/home/code/mcp_server.php'],
            ])->tools(),
        ];
    }
}
```

## Remote MCP Server

### Streamable HTTP Server

Remote servers are accessible via URLs and typically require authentication. You can use the `token` field in the configuration array, which will be used as the authorization token to authenticate on the server:

```php
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    ...
    
    protected function tools(): array
    {
        return [
            ...McpConnector::make([
                'url' => 'https://mcp.example.com',
                'token' => 'BEARER_TOKEN',
                'timeout' => 30,
                'headers' => [
                    //'x-cutom-header' => 'value'
                ]
            ])->tools(),
        ];
    }
}
```

### SSE HTTP Transport

SSE ([Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)) is a mechanism that allows web clients to receive automatic updates from a server. Those updates are known as "events", and are sent over a single, long-lived HTTP connection.

To use the SSE transport you need to set `async ⇒ true` in the configuration parameters.

```php
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    ...
    
    protected function tools(): array
    {
        return [
            ...McpConnector::make([
                'url' => 'https://mcp.example.com',
                'token' => 'BEARER_TOKEN',
                'timeout' => 30,
                'async' => true
            ])->tools(),
        ];
    }
}
```

## Monitoring & Debugging

To stay updated about your Agent decision making process, you can connect the [Inspector monitoring dashboard](https://inspector.dev/) to monitor tool selection and execution in real-time.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

When your agent runs you will be able to explore the execution timeline in the dashboard.

<figure><img src="/files/kyDct4iL6iFYUjNGb0Je" alt=""><figcaption></figcaption></figure>

## Filter the list of tools

During connection with complex MCP servers they can includes tools that could lead to undesired behavior in specific contexts. The `exclude()` and `only()` methods address this challenge elegantly, allowing developers to connect with comprehensive MCP servers while maintaining fine-grained control over available capabilities you want to provide to your agent.&#x20;

This becomes particularly useful when working with specialized agents that need specific capabilities but you want to reduce the probability of an agent mistake, and reduce tokens consumption.

These methods accept a list of tool names that you do or do not want to associate with the agent.

```php
class MyAgent extends Agent 
{
    ...
    
    protected function tools()
    {
        return [
            // EXCLUDE: discard certain tools
            ...McpConnector::make([
                'url' => 'https://mcp.example.com',
            ])->exclude([
                'tool_name_1',
                'tool_name_2',
            ])->tools(),
            
            // ONLY: Select the tools you want to include
            ...McpConnector::make([
                'url' => 'https://mcp.example.com',
            ])->only([
                'tool_name_1',
                'tool_name_2',
            ])->tools(),
        ];
    }
}
```


# Chat History

Learn how Neuron AI manage multi turn conversations.

Neuron AI provides you with a built-in system to manage the memory of a chat session you perform with the agent.

In many Q\&A applications you can have a back-and-forth conversation with the LLM, meaning the application needs some sort of "memory" of past questions and answers, and some logic for incorporating those into its current thinking.

For example, if you ask a follow-up question like "Can you elaborate on the second point?", this cannot be understood without the context of the previous message. Therefore we can't effectively perform retrieval with a question like this.

In the example below you can see how the Agent doesn't know my name initially:

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

$response = Agent::make()->chat(new UserMessage("What's my name?"));

echo $response->getContent();
// I'm sorry I don't know your name. Do you want to tell me more about yourself?
```

Clearly the Agent doesn't have any context about me. Now I try present me in the first message, and then ask for my name:

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

$agent = Agent::make()

$response = $agent->chat(
    new UserMessage("Hi, my name is Valerio!")
);
echo $response->getContent();
// Hi Valerio, nice to meet you, how can I help you today?


$response = $agent->chat(
    new UserMessage("Do you remember my name?")
);
echo $response->getContent();
// Sure, your name is Valerio!
```

## How Chat History works

Neuron Agents take the list of messages exchanged between your application and the LLM into an object called Chat History. It's a crucial part of the framework because the chat history needs to be managed based on the context window of the underlying LLM.

It's important to send past messages back to LLM to keep the context of the conversation, but if the list of messages grows enough to exceed the context window of the model the request will be rejected by the AI provider.

Chat history automatically truncates the list of messages to never exceed the context window avoiding unexpected errors.

## How to feed a previous conversation

Sometimes you already have a representation of user to assistant conversation and you need a way to feed the agent with previous messages.

You just need to pass an array of messages to the \`chat()\` method. This conversation will be automatically loaded into the agent memory and you can continue to iterate on it.

```php
use NeuronAI\Chat\Enums\MessageRole;
use NeuronAI\Chat\Messages\Message;

$response = MyAgent::make()
    ->chat([
        new Message(MessageRole::USER, "Hi, my company is called Inspector.dev"),
        new Message(MessageRole::ASSISTANT, "Great, how can I assist you today?"),
        new Message(MessageRole::USER, "What's the name of the company I work for?"),
    ]);
    
echo $response->getContent();
// You work for Inspector.dev
```

The last message in the list will be considered the most recent.

## Register the chat history

By default Neuron Agent uses an "in memory" chat history. That means it keeps messages only for the current execution cycle. But, if you want to persist messages across sessions you can tell the agent to use a different component by implementing the `chatHistory` method in the Agent class.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\InMemoryChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        ...
    }
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new InMemoryChatHistory(
            contextWindow: 50000
        );
    }
}
```

{% hint style="warning" %}
The internal `TokenCounter` component estimates token usage for each message. Actual usage may vary between providers, so we recommend configuring the `contextWindow` value slightly below the maximum supported by your provider to avoid unexpected API errors.
{% endhint %}

## Available Chat History Implementations

### InMemoryChatHistory

It simply store the list of messages into an array. It is kept in memory only during the current execution. It's used by default if you don't explicitly register another component.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\InMemoryChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new InMemoryChatHistory(
            contextWindow: 50000
        );
    }
}
```

### FileChatHistory

This compnent makes you able to persist the ongoing conversation with the agent in a file, and resume it later in time. To create an instance of the `FileChatHistory` you need to pass the absolute path of the `directory` where you want to store conversations, and the unique `key` for the current conversation.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\FileChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new FileChatHistory(
            directory: '/home/app/storage/neuron',
            key: 'THREAD_ID',
            contextWindow: 50000
        );
    }
}
```

The `key` parameter allows you to store different files to separate conversations. You can use a unique key for each user, or the ID of a thread to make users able to store multiple conversations.

### SQLChatHistory

This component allows you to store the ongoing conversation into a SQL database. Before using this component you must create the table on your database to store messages. Here is the SQL script:

```sql
CREATE TABLE chat_history (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  thread_id VARCHAR(255) NOT NULL,
  messages LONGTEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 
  UNIQUE KEY uk_thread_id (thread_id),
  INDEX idx_thread_id (thread_id)
);
```

You can customize this table addind more columns eventually to add a relation to your users or similar use cases. You can also customize the table name passing your custom one when creating the instance.

To create an instance of the `SQLChatHistory` you need to pass the `thread_id` to separate different conversation threads, and the `PDO` connection to the database.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: 'THREAD_ID',
            pdo: new \PDO("mysql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            table: 'chat_history',
            contextWindow: 50000
        );
    }
}
```

If your application is built on top of a framewrok you can easily get the PDO connection from the ORM. Here are is couple of examples in the context of Laravel or Symfony applications.

#### Laravel

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: 'CHAT_THREAD_ID',
            pdo: \DB::connection()->getPdo(),
            table: 'chat_history',
            contextWindow: 50000
        );
    }
}
```

#### Symfony

You can register your agent as a service with an instance of `Doctrine\DBAL\Connection` as a constructor dependency:

```php
namespace App\Neuron;

use Doctrine\DBAL\Connection;
use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    public function __construct(protected Connection $connection)
    {}
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: 'CHAT_THREAD_ID',
            pdo: $this->connection->getNativeConnection(),
            table: 'chat_history',
            contextWindow: 50000
        );
    }
}
```

### EloquentChatHisotry

You should create your own Eloquent model and pass the class string as the constructor argument. The model can have custom relations, scopes, attributes, etc. but the basic structure must be based on this migration script:

```bash
php artisan make:migration create_chat_messages_table --create=chat_messages
```

```php
Schema::create('chat_messages', function (Blueprint $table) {
     $table->id();
     $table->string('thread_id');
     $table->string('role');
     $table->json('content')->nullable();
     $table->json('meta')->nullable();
     $table->timestamps();

     $table->index(['thread_id', 'id']); // For efficient ordering and trimming
});
```

#### Example ChatMessage model

```php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ChatMessage extends Model
{
    protected $fillable = [
        'thread_id', 'role', 'content', 'meta'
    ];
    
    protected $casts = [
        'content' => 'array', 
        'meta' => 'array'
    ];
    
    /**
     * The conversation that owns the chat message.
     *
     * @return BelongsTo<Conversation, $this>
     */
    public function conversation(): BelongsTo
    {
        return $this->belongsTo(Conversation::class, 'thread_id');
    }
}
```

#### Use in your agent

```php
namespace App\Neuron;

use App\Models\ChatMessage;
use NeuronAI\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\EloquentChatHistory;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new EloquentChatHistory(
            threadId: 'THREAD_ID',
            modelClass: ChatMessage::class,
            contextWindow: 50000
        );
    }
}
```

## Implement custom chat history

You can create a custom implementation of the chat history to support different persistent layer just  implementing `AbstractChatHistory`. It allows you to inherit several behaviors for the internal history management, so you have just to implement a couple of methods to save messages into the storage system you want to use.

```php
class MyChatHistory extends AbstractChatHistory
{
    /**
     * @param Message[] $messages
     */
    public function setMessages(array $messages): ChatHistoryInterface
    {
        // Store all messages at once every time the hisotry is updated
    }

    protected function clear(): ChatHistoryInterface
    {
        // Empty the hisotry
    }

    protected function onNewMessage(Message $message): void
    {
        // Handle single message addition
    }

    protected function onTrimHistory(int $index): void
    {
        // When the trim is triggered, the messages in the position from zero to the index must be removed.
    }
}
```

The abstract class already implement some utility methods to calculate tokens usage based on the AI provider responses and automatically cut the conversation based on the size of the context window. You just have to focus on the interaction with the underlying storage to add and remove messages, or clear the entire history.

We strongly suggest to look at other implementations like `FileChatHistory` to understand how to create your own.

### Serialize/Deserialize Messages

When the ChatHistory needs to store a message it must be serialized. The same way, when the ChatHistory component is instantiated it should load all the previous messages from the underlying storage (database, cache, etc) and deserialize them to the original message type.

To serialize/deserialize messages consistently the `AbstractChatHistory` provides you with `jsonSerialize()` and `deserializeMessages()` methods. Here is an example of how to use them in an hypothetical database chat history implementation:

```php
<?php

namespace NeuronAI\Chat\History;

use NeuronAI\Chat\Messages\Message;

class DatabaseChatHistory extends AbstractChatHistory
{
    public function __construct(protected \PDO $db) 
    {
        // Retrieve the current conversation from the underlying storage
        $messages = $this->db->select(...);
        
        // Deserialize properly initialize the correct message types with the correct data.
        $this->history = $this->deserializeMessages($messages);
    }

    protected function onNewMessage(Message $message): void
    {
        // Store the json version.
        $this->db->insert($message->jsonSerialize());
    }

    ...
}
```


# Streaming

Presenting AI response to your user in real-time.

Streaming enables you to show users chunks of response text as they arrive rather than waiting for the full response. You can offer a real-time Agent conversation experience.

<figure><img src="/files/b2ldC0sehofX9NeBePUB" alt=""><figcaption></figcaption></figure>

### Agent

To stream the AI response you should use the `stream()` method to run the agent, instead of `chat()`. This method return a PHP generator that can be used to process the response as an iterable object.

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Messages\UserMessage;

$stream = MyAgent::make()->stream(
    new UserMessage('How are you?')
);

// Print the response chunk-by-chunk in real-time
foreach ($stream as $text) {
    echo $text;
}

// I'm fine, thank you! How can I assist you today?
```

### Streaming & Tools

Neuron support Tools & Function calls in combination with the streaming response. You are free to provide your Agents with Tools and they will be automatically handled in the middle of the stream, to continue toward the final response.

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Tools\Tool;

$stream = MyAgent::make()
    ->addTool(
        Tool::make(
            'get_server_configuration',
            'retrieve the server network configuration'
        )->addProperty(...)->setCallable(...)
    )
    ->stream(
        new UserMessage("What's the IP address of the server?")
    );

// Iterate chunks
foreach ($stream as $chunk) {
    if ($chunk instanceof ToolCallMessage) {
        // Output the ongoing tool call
        echo PHP_EOL.\array_reduce(
            $chunk->getTools(), 
            fn(string $carry, ToolInterface $tool) 
                => $carry .= '- Calling tool: '.$tool->getName().PHP_EOL, 
            '');
    } else {
        echo $chunk;
    }
}

// Let me retrieve the server configuration. 
// - Calling tool: get_server_configuration
// The IP address of the server is: 192.168.0.10
```

### Monitoring & Debugging

To watch inside this workflow you should connect your Agent to the [Inspector monitoring dashboard](https://inspector.dev/) in order to see the tool call execution flow in real-time.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

<figure><img src="/files/k5mOdYZtT5IxdKnPf3Hf" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Learn more about Agent observability in the [dedicated documentation](/v2/the-basics/observability).
{% endhint %}


# Structured Output

Enforce the Agent output based on the provided schema.

{% hint style="info" %}

### PREREQUISITES

This guide assumes you are already familiar with the following concepts:

* [Agent](/v2/the-basics/agent)
* [Tool & Function Call](/v2/the-basics/tools)
  {% endhint %}

There are many use cases where we need Agents to understand natural language, but output in a *structured format*. One common use-case is extracting data from text to insert into a database or use with some other downstream system. This guide covers how Neuron allows you to enforce structured outputs from the agent.

<figure><img src="/files/ay6HaG4cgkzAarsQaZHc" alt=""><figcaption></figcaption></figure>

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

### How to use Structured Output

The central concept is that the output structure of LLM responses needs to be represented in some way. The schema that Neuron validates against is defined by PHP type hints. Basically you have to define a class with strictly typed properties:

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;

class Person 
{
    #[SchemaProperty(description: 'The user name.', required: true)]
    public string $name;
    
    #[SchemaProperty(description: 'What the user love to eat.', required: false)]
    public string $preference;
}
```

Neuron generates the corresponding JSON schema from the PHP object to instruct the underlying model about your required data format. Then the agent parse the LLM output to extract data and returns an object instance filled with appropriate values:

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

// Talk to the agent requiring the structured output
$person = MyAgent::make()->structured(
    new UserMessage("I'm John and I like pizza!"),
    Person::class
);

echo $person->name.' like '.$person->preference;
// John like pizza
```

### Default output class

You can also encapsulate the output format into the Agent implementation, so it will be the Agent standard output format. You always need to call the `structured()` method to require strict output.

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

// Encapsulate the default output format 
class MyAgent extends Agent
{
    ...

    protected function getOutputClass(): string
    {
        return Person::class;
    }
}

// Always use the structured method if you want to get structured output
$person = MyAgent::make()
    ->structured(new UserMessage("I'm John and I like pizza"));

echo $person->name.' like '.$person->preference;
// John like pizza
```

### Control the output generation

Neuron requires you to define two layers of rules to create the structured output class.&#x20;

The first is the `SchemaProperty` attribute that allows you to control the JSON schema sent to the LLM to understand the required data format.

The second layer is validation. Validation attributes will ensure data gathered from the LLM response are consistent with your requirements.

<figure><img src="/files/7QsgZugLMF1TyhHoDIII" alt=""><figcaption></figcaption></figure>

### SchemaProperty

We strongly recommend to use the `SchemaProperty` attribute to define at least the description, to allow the LLM understand the purpose of a property, and the required flag:

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;

class Person 
{
    #[SchemaProperty(description: 'The user name.', required: true)]
    public string $name;
    
    #[SchemaProperty(description: 'What the user love to eat.', required: false)]
    public string $preference;
}
```

### Validation

The Validation component already contains many validation rules that you can apply to the output class properties. The example below shows you how to mark the name property as required (*NotBlank*):

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Person 
{
    #[SchemaProperty(description: 'The user name.')]
    #[NotBlank]
    public string $name;
    
    #[SchemaProperty(description: 'What the user love to eat.')]
    public string $preference;
}
```

### Nested Class

You can construct complex output structures using other PHP objects as a property type. Following the example of a the `Person` class we can add the `address` property typed as another structured class.

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Property;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Valid;

class Person 
{
    #[SchemaProperty(description: 'The user name.', required: true)]
    #[NotBlank]
    public string $name;
    
    #[SchemaProperty(description: 'What user love to eat.', required: true)]
    public string $preference;
    
    #[SchemaProperty(description: 'The address to complete the delivery.', required: true)]
    public Address $address;
}
```

In the `Address` definition we require only the street and zip code properties, and allow city to be empty.&#x20;

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Address
{
    #[SchemaProperty(description: 'The name of the street.', required: true)]
    #[NotBlank]
    public string $street;

    #[SchemaProperty(description: 'The name of the city.', required: false)]
    public string $city;

    #[SchemaProperty(description: 'The zip code of the address.', required: true)]
    #[NotBlank]
    public string $zip;
}
```

Now when you ask the agent for the structured output you will get the filled instance back:

<pre class="language-php"><code class="lang-php">use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Observability\AgentMonitoring;

<strong>// Talk to the agent requiring the structured output
</strong>$person = MyAgent::make()->structured(
    new UserMessage("I'm John and I want a pizza at st. James Street 00560!"),
    Person::class
);

echo $person->name.' like '.$person->preference.'. Address: '.$person->address->street;
// John like pizza. Address: st.James Street
</code></pre>

## Array

If you declare a property as an array Neuron assumes the list of items to be a list of string. Assume we want to add a list of tags to the Person object:

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Person 
{
    #[SchemaProperty(description: 'The user name.', required: true)]
    #[NotBlank]
    public string $name;
    
    #[SchemaPropertyerty(description: 'What user love to eat.', required: true)]
    public string $preference;
    
    #[SchemaProperty(description: 'The list of tag for the user profile.', required: true)]
    public array $tags;
}
```

Without any additional information the agent will assume that the `tags` property is an array of strings by default.&#x20;

```php
echo $person->tags;

/*
[
    'tag 1',
    'tag 2',
    ...
]
*/
```

### Array of objects

It could be needed to populate the list of tags with another structured data type. To do this you must add the `ArrayOf` attribute for properly validation, and specify the fully qualified class namespace in the doc-block for properly deserialization:

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;
use NeuronAI\StructuredOutput\Validation\Rules\ArrayOf;

class Person 
{
    ...
    
    /**
     * @var \App\Agent\Models\Tag[]
     */
    #[SchemaProperty(description: 'The list of tag for the user profile.', required: true)]
    #[ArrayOf(Tag::class)]
    public array $tags;
}
```

And here is the hypotetical implementation of the `Tag` class with its own validation rules and property info:

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Tag
{
    #[SchemaProperty(description: 'The name of the tag', required: true)]
    #[NotBlank]
    public string $name;
}
```

### Multiple object types

Neuron also supports the composition of arrays with multiple object types. You have two options to specify the PHP classes you want to build the array with:

Using the square brackets syntax:

```php
class Person 
{
    ...
    
    /**
     * @var \App\TextBlock[]|\App\TableBlock[]
     */
    #[SchemaProperty(description: 'The list of tag for the user profile.', required: true)]
    #[ArrayOf([TextBlock::class, TableBlock::class])]
    public array $tags;
}
```

Or using the "array<...>" syntax:

```php
class Person 
{
    ...
    
    /**
     * @var array<\App\TextBlock|\App\TableBlock>
     */
    #[SchemaProperty(description: 'The list of tag for the user profile.', required: true)]
    #[ArrayOf([TextBlock::class, TableBlock::class])]
    public array $tags;
}
```

As you can notice from the examples above you can pass an array of class-string to the `ArrayOf` validation rule to ensure the final array will contains only instances of the listed classes.

## Max Retries

Since the LLM are not perfectly deterministic it's mandatory to have a retry mechanism in place if something is missing in the LLM response.

By default Neuron extracts and validates the data from the LLM response and if there is one or more validation errors automatically retry the request just one more time informing the LLM about what went wrong and for what properties.&#x20;

You can eventually customize the number of times the agent must retry to get a correct answer from the LLM:

```php
$person = MyAgent::make()->structured(
    messages: new UserMessage("I'm John and I like pizza!"),
    class: Person::class,
    maxRetries: 3
);
```

If you work with a less capable LLM consider to use a number of retries balancing the probability to get e valid answer, and the potential token consumption.

You can disable retry just passing zero. It will be a one shot attempt:

```php
$person = MyAgent::make()->structured(
    messages: new UserMessage("I'm John and I like pizza!"),
    class: Person::class,
    maxRetries: 0
);
```

## Monitoring & Debugging

To watch inside this workflow you should connect your Agent to the [Inspector monitoring dashboard](https://inspector.dev/) in order to see the tool call execution flow in real-time.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

<figure><img src="/files/ocFjYUpxzm7KiWQnL2HF" alt=""><figcaption></figcaption></figure>

Each segment bring its own debug information to follow the agent execution in real time:

<figure><img src="/files/KYNroOaw04zEESiBGUtP" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Learn how to enable [**observability**](/v2/the-basics/observability) in the next section.
{% endhint %}

## Available Validation Rules

### #\[NotBlank]

The property under validation cannot be blank. It accept the `allowNull` flag to treat explicitly null value as empty equivalent or not.

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Person 
{
    #[NotBlank(allowNull: false)]
    public string $name;
}
```

### #\[Length]

Determine if the length of a `string` match the given criteria:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Length;

class Person 
{
    #[Length(min: 1, max: 10)]
    public string $name;
    
    #[Length(exactly: 5)]
    public string $zip_code;
}
```

### #\[WordsCount]

Determine if the number of words in a `string` match the given criteria:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\WordsCount;

class Person 
{
    #[WordsCount(exactly: 10)]
    public string $title;
    
    #[WordsCount(min: 1, max: 10)]
    public string $content;
}
```

### #\[Count]

Determine if the size of an `array` match the given criteria:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Count;

class Person 
{
    #[Count(min: 1, max: 3)]
    public array $dogs;
    
    #[Count(exactly: 1)]
    public array $children;
}
```

### #\[EqualTo] - #\[NotEqualTo]

These rules have the same structure and meaning, and accept a single argument to define the value to compare against. The property under validation must be strictly equal (*#\[EqualTo]*) or different (*#\[NotEqualTo]*) than the reference value:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\EqualTo;
use NeuronAI\StructuredOutput\Validation\Rules\NotEqualTo;

class Person 
{
    #[EqualTo(reference: 'Rome')]
    public string $city;
    
    #[NotEqualTo(reference: '00502')]
    public string $zip_code;
}
```

### #\[GreaterThan] - #\[GreaterThanEqual]

These rules have the same structure and meaning, and accept a single argument to define the value to compare against. The property under validation must be strictly greater (*#\[GreaterThan]*) or equal (*#\[GreaterThanEqual]*) than the reference value:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\GreaterThan;
use NeuronAI\StructuredOutput\Validation\Rules\GreaterThanEqual;

class Person 
{
    #[GreaterThan(reference: 17)]
    public int $age;
    
    #[GreaterThanEqual(reference: 1)]
    public int $cars;
}
```

### #\[LowerThan] - #\[LowerThanEqual]

These rules have the same structure and meaning, and accept a single argument to define the value to compare against. The property under validation must be strictly lower (*#\[LowerThan]*) or equal (*#\[LowerThanEqual]*) than the reference value:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\LowerThan;
use NeuronAI\StructuredOutput\Validation\Rules\LowerThanEqual;

class Person 
{
    #[LowerThan(reference: 50)]
    public int $age;
    
    #[LowerThanEqual(reference: 1)]
    public int $cars;
}
```

### #\[OutOfRange]

Determin if a `number` is out of the given range:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\InRange;

class Person 
{
    #[OutOfRange(min: 18, max: 35)]
    public int $age;
    
    // The strict argument force to stay stricly out of the range limits
    #[OutOfRange(min: 48, max: 54, strict: true)]
    public int $size;
}
```

### #\[IsFalse] - #\[IsTrue]

The property under validation must have exactly the boolean value defined by the rule:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\IsFalse;
use NeuronAI\StructuredOutput\Validation\Rules\IsTrue;

class Phone
{
    #[IsFalse]
    public bool $iphone;
    
    #[IsTrue]
    public bool $refurbed;
}
```

### #\[IsNull] - #\[IsNotNull]

The property under validation must respect the nullable condition defined by the rule:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\IsNotNull;
use NeuronAI\StructuredOutput\Validation\Rules\IsNull;

class Phone
{
    #[IsNotNull]
    public string $brand;
    
    #[IsNull]
    public ?string $test;
}
```

### #\[Json]

The property under validation must contains a valid JSON string:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Json;

class Person
{
    #[Json]
    public string $address;
}
```

### #\[Url]

The property under validation must contains a valid URL:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Url;

class Person
{
    #[Url]
    public string $website;
}
```

### #\[Email]

The property under validation must contains a valid Email address:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Email;

class Person
{
    #[Email]
    public string $email;
}
```

### #\[IpAddress]

The property under validation must contains a valid IP address:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\IpAddress;

class Person
{
    #[IpAddress]
    public string $ip;
}
```

### #\[ArrayOf]

The property under validation must be an array that contains all of the given types of objects. Notice that you also need to add the doc-block in order to make the agent able to instance the correct class. Use the full class namespace in the doc-block.

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\ArrayOf;

class Person
{
    /**
     * @var \App\Neuron\Output\Tag[]
     */
    #[ArrayOf(Tag::class)]
    public array $tags;
}
```


# Attachments (Documents & Images)

Attach documents and images to your message.

Most advanced LLMs can understand the content of documents and images other than simple text. With Neuron you can attach files to your messages to enrich the context provided to the Agent.

The most common use cases for documents analysis are:

* Caption and answer questions about images
* Transcribe and reason over document contents

You have two options to attach items to your messages: as an URL, or encoded in base64.

{% hint style="warning" %}
Be sure about the possible limitations of your AI provider to handle documents and images in specific formats.&#x20;
{% endhint %}

### Documents

#### URL

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Attachments\Document;
use NeuronAI\Chat\Messages\UserMessage;

// Ollama only support images encoded in base64
$message = (new UserMessage("Describe this document"))
    ->addAttachment(
        new Document('https://url_of/document.pdf')
    );
    
$response = MyAgent::make()->chat($message);
// The document is a contract...
```

#### Base64

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Attachments\AttachmentContentType;
use NeuronAI\Chat\Attachments\Document;
use NeuronAI\Chat\Messages\UserMessage;

$content = base64_encode(file_get_contents('/document.pdf'));

$message = (new UserMessage("Describe this document"))
    ->addAttachment(
        new Document(
            document: $content,
            type: AttachmentContentType::BASE64,
            mediaType: 'application/pdf'
        )
    );
    
$response = MyAgent::make()->chat($message);
// The document is a contract...
```

### Images

#### URL

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Attachments\Image;
use NeuronAI\Chat\Messages\UserMessage;

// Ollama only support images encoded in base64
$message = (new UserMessage("Describe this image"))
    ->addAttachment(
        new Image('https://url_of/image.jpg')
    );
    
$response = MyAgent::make()->chat($message);
// The image shows...
```

#### Base64

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Attachments\AttachmentContentType;
use NeuronAI\Chat\Attachments\Image;
use NeuronAI\Chat\Messages\UserMessage;

$content = base64_encode(file_get_contents('/image.jpg'));

$message = (new UserMessage("Describe this image"))
    ->addAttachment(
        new Image(
            image: $content,
            type: AttachmentContentType::BASE64,
            mediaType: 'image/jpeg'
        )
    );
    
$response = MyAgent::make()->chat($message);
// The image shows...
```

### File ID

Usually you can attach files to your message (images or documents) as URLs, or encoded in base64 format. Many provider allows you to upload files on their platform once, and reference these files with a simple ID on the message. This can unlock big savings in token consumption and can improve the model response time.

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Attachments\AttachmentContentType;
use NeuronAI\Chat\Attachments\Image;
use NeuronAI\Chat\Messages\UserMessage;

$message = (new UserMessage("Describe this document"))
    ->addAttachment(
        new Document(
            document: $document_id,
            type: AttachmentContentType::ID,
        )
    );
    
$response = MyAgent::make()->chat($message);
// The document is about...
```

## Ollama limitations

Ollama only support images in base64 format, so you have to take care to convert the file content and set up the right type for attachments:

```php
use NeuronAI\Chat\Attachments\AttachmentContentType;
use NeuronAI\Chat\Attachments\Image;
use NeuronAI\Chat\Messages\UserMessage;

// Ollama only support images encoded in base64
$message = (new UserMessage("Describe this image"))
    ->addAttachment(
        new Image(
            image: 'base64-encoded-content', 
            type: AttachmentContentType::BASE64, 
            mediaType: 'image/jpeg'
        )
    );
```


# Async

Execute multiple parallel processes using Neuron async interface.

Neuron supports asynchronous execution and parallel processing of agent requests, enabling you to efficiently handle multiple operations simultaneously. This is particularly valuable for batch processing, data classification pipelines, and high-throughput applications.

### Why Use Async Processing?

Asynchronous processing addresses several common challenges in AI-powered applications:

**Performance Optimization**: Instead of waiting for each request to complete sequentially, you can process multiple inputs simultaneously, dramatically reducing total execution time.

**Cost Efficiency**: When working with token-based pricing models, parallel processing allows you to maximize throughput within rate limits and optimize your API usage costs.

**Scalability**: Applications handling large volumes of data (product classification, content moderation, data labeling) benefit significantly from concurrent processing capabilities.

**User Experience**: In web applications, async processing prevents blocking operations that could impact response times and user experience.

**Provider Independence**: Unlike batch processing features that are provider-specific (such as OpenAI's Batch API), async processing is implemented at the framework level, making it available for all providers out of the box without relying on individual provider capabilities or implementations.

### Framework-Level vs Provider-Level Solutions

Neuron's async processing approach offers several advantages over provider-specific batch APIs:

**Universal Compatibility**: Async processing works with any provider supported by Neuron, regardless of whether they offer native batch processing capabilities.

**Consistent Interface**: You use the same async methods and patterns across all providers, eliminating the need to learn different batch implementations for each service.

**Future-Proof**: As new providers are added to Neuron, they automatically inherit async processing capabilities without requiring additional implementation work.

**Fallback Support**: Even if a provider discontinues or changes their batch API, your async implementation continues to work unchanged.

### Basic Async Implementation

To execute multiple agent requests in parallel, create separate agent instances for each operation and schedule the async execution using `chatAsync` method instead of the normal `chat` method. This prevents state conflicts and ensures clean execution:

```php
use GuzzleHttp\Promise\Utils;
use NeuronAI\Chat\Messages\UserMessage;

// Create separate agent instances
$agent1 = ClassificationAgent::make();
$agent2 = ClassificationAgent::make();
$agent3 = ClassificationAgent::make();

// Execute multiple parallel requests
$results = Utils::unwrap([
    'product_a' => $agent1->chatAsync(new UserMessage("Classify: Red cotton shirt, size M")),
    'product_b' => $agent2->chatAsync(new UserMessage("Classify: wireless headphones, Bluetooth 5.3")),
    'product_c' => $agent3->chatAsync(new UserMessage("Classify: laptop, Intel i7, 16GB RAM"))
]);

// Access results
echo $results['product_a']->getContent();
echo $results['product_b']->getContent();
echo $results['product_c']->getContent();
```

{% hint style="warning" %}
**Instance Isolation**: Always use separate agent instances for parallel requests. Reusing the same instance can cause state conflicts and unpredictable behavior.
{% endhint %}

### Queue-Worker Processing

For applications using message queues (RabbitMQ, Redis, SQS, etc.), async processing integrates seamlessly with worker patterns. The example below is like a pseudo-code representing a background Job to process the classification of multiple products in parallel.&#x20;

You will implement your queue-worker pattern using the services provided by your framework. This is just a guideline on you can encapsulate this process:

```php
class ProductClassificationWorker
{
    public function handle(ClassificationJob $job, Inspector $inspector): void
    {
        $agents = [];
        $promises = [];
        
        // Prepare async requests
        foreach ($job->products as $id => $product) {
            $agents[$id] = ClassificationAgent::make();
            $promises[$id] = $agents[$id]->chatAsync(
                new UserMessage($product->getDescription())
            );
        }
        
        // Wait for all responses
        $results = Utils::unwrap($promises);
        
        // Process results
        foreach ($results as $id => $response) {
            // Save $response->getContent() for product ID $id
        }
    }
}
```

### Error Handling in Async Operations

When working with multiple concurrent requests, implement robust error handling to manage partial failures:

```php
use GuzzleHttp\Promise\Utils;
use NeuronAI\Exceptions\AgentException;

try {
    
    $responses = Utils::unwrap($promises);
    
} catch (AgentException $exception) {
    // Handle specific agent errors
}
```

### Performance Considerations

Asynchronous processing in Neuron enables you to build scalable, efficient AI-powered applications that can handle high-volume workloads while maintaining optimal performance and resource utilization.

**Memory Usage**: Each agent instance consumes memory. For very large batches, consider processing in smaller chunks to manage memory consumption.

**Rate Limits**: Be mindful of API rate limits when processing large volumes. Implement appropriate delays or throttling if needed.

### Monitoring & Debugging

For processes that run multiple async requests you have to explicitly ask for splitting the monitoring data for each process to avoid wrong association because of the concurrenct execution. To do it you need to set NEURON\_SPLIT\_MONITORING environment variable to `true`.&#x20;

You can set it as an environment variable if your background process runs in isolation:

{% code title=".env" %}

```
NEURON_SPLIT_MONITORING=true
```

{% endcode %}

Or set it on the fly before executing the concurrent requests:

```php
$_ENV['NEURON_SPLIT_MONITORING'] = true;

// Execute multiple parallel requests
$results = Utils::unwrap([
    'product_a' => $agent1->chatAsync(new UserMessage("Classify: Red cotton shirt, size M")),
    'product_b' => $agent2->chatAsync(new UserMessage("Classify: wireless headphones, Bluetooth 5.3")),
    'product_c' => $agent3->chatAsync(new UserMessage("Classify: laptop, Intel i7, 16GB RAM"))
]);
```


# Monitoring & Debugging

Monitor your AI Agents, RAGs, and Workflows in real-time.

### The Problem With AI Systems

Integrating AI Agents into your application you’re not working only with functions and deterministic code, you program your agent also influencing probability distributions. Same input ≠ output. That means reproducibility, versioning, and debugging become real problems.

Many of the Agents you build with Neuron will contain multiple steps with multiple invocations of LLM calls, tool usage, access to external memories, etc. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly your agent is doing and why.&#x20;

Why is the model making certain decisions? What data is the model reacting to? Prompting is not programming in the common sense. No static types, small changes break output, long prompts cost latency, and no two models behave exactly the same with the same prompt.

The [Inspector](https://inspector.dev/) team designed Neuron with built-in observability features, so you can monitor AI agents running, helping you maintain production-grade implementations with confidence.

## Get Started With Inspector

To start monitoring your Agents you need to add the `INSPECTOR_INGESTION_KEY` variable in your application environment file. Authenticate on [app.inspector.dev](https://app.inspector.dev/register) to create a new one.

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

When your agents are being executed, you will see the details of their internal steps on the Inspector dashboard.

<figure><img src="/files/Bb5QhlnIr1o5ELFDIbwO" alt=""><figcaption></figcaption></figure>

If you want to monitor the whole application you can install the Inspector package based on your development environment. We provide integration packages for [PHP](https://github.com/inspector-apm/inspector-php), [Laravel](https://github.com/inspector-apm/inspector-laravel), [Symfony](https://github.com/inspector-apm/inspector-symfony), [CodeIgniter](https://github.com/inspector-apm/inspector-codeigniter), [Drupal](https://docs.inspector.dev/guides/drupal). Check out them on our [GitHub organization](https://github.com/inspector-apm).

### Create An Ingestion Key

To create an Ingestion key head to the [**Inspector dashboard**](https://app.inspector.dev/register) and create a new app.

{% hint style="success" %}
For any additional support drop in a live chat in the dashboard. We are happy to listen from your experience, find new possible improvements, and make the tool better overtime.
{% endhint %}

## Logging

If you want to report agent activity into your log system you can attach the built-in `LogObserver` to your agent passing an instance of a PSR `LoggerInterface` compatible logger, like monolog for example:

```php
use NeuronAI\Observability\LogObserver;

$agent = MyAgent::make()->observe(
    new LogObserver($logger)
);
```

All itnernal events with their payload will be logged.


# Error Handling

Managing errors fired by your agent.

All exceptions fired from Neuron AI  are an extension of `NeuronException` . There are several types of exceptions that can help you understand what's going wrong, but because they inherit from the same root exception, they give you the ability to accurately detect agent errors in the context of your code:

```php
try {

    // Your code here...

} catch (NeuronAI\Exceptions\NeuronException $e) {
    // ...
} catch (NeuronAI\Exceptions\ProviderException $e) {
    // ...
} catch (NeuronAI\Exceptions\AgentException $e) {
    // ...
} catch (NeuronAI\Exceptions\ChatHistoryException $e) {
    // ...
} catch (NeuronAI\Exceptions\HttpException $e) {
    // ...
} catch (NeuronAI\Exceptions\ToolException $e) {
    // ...
} catch (NeuronAI\Exceptions\VectorStoreException $e) {
    // ...
} catch (NeuronAI\Exceptions\WorkflowException $e) {
    // ...
} catch (NeuronAI\Exceptions\DataReaderException $e) {
    // ...
}
```

### Monitoring & Debugging

If you want to be alerted on any error, consider to connect [**Inspector**](https://inspector.dev/) to your application.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file.

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

The Agent will automatically instrument itself. Learn more on the [documentation](/v2/the-basics/observability) for other configuration options.

<figure><img src="/files/zDYa7yjaRy1p0fS7e8QA" alt=""><figcaption></figcaption></figure>


# Evaluation

Evaluating the output of your agentic system

This guide covers approaches to evaluating agents. Effective evaluation is essential for measuring agent performance, tracking improvements, and ensuring your agents meet quality standards.

When building AI agents, evaluating their performance is crucial during this process. It's important to consider various qualitative and quantitative factors, including response quality, task completion, success, and inaccuracies or hallucinations. In evaluations, it's also important to consider comparing different agent configurations to optimize for specific desired outcomes. Given the dynamic and non-deterministic nature of LLMs, it's also important to have rigorous and frequent evaluations to ensure a consistent baseline for tracking improvements or regressions.

### Configuring your application

Like unit tests, it could be better to collect evaluators for your AI system into a dedicated directory. So, you can add the configuration below to your application composer.json file:

```json
"autoload-dev": {
    "psr-4": {
        ...,
        "App\\Evaluators\\": "evaluators/"
    }
},
```

And create the `evaluators` directory in your project root folder. Keeping test code separate from production code creates a clear boundary between what gets deployed to production and what exists purely for development and quality assurance.

### Creating Evaluator

Create the AgentEvaluator class into the evaluators folder:

```php
namespace App\Evaluators;

use NeuronAI\Evaluation\Assertions\StringContains;
use NeuronAI\Evaluation\BaseEvaluator;
use NeuronAI\Evaluation\Contracts\DatasetInterface;
use NeuronAI\Evaluation\Dataset\JsonDataset;

class AgentEvaluator extends BaseEvaluator
{
    public function getDataset(): DatasetInterface
    {
        return new JsonDataset(__DIR__ . '/datasets/dataset.json');
    }

    public function run(array $datasetItem): mixed
    {
        $response = MyAgent::make()->chat(
            new UserMessage($datasetItem['input'])
        );
        
        return $response->getContent();
    }

    public function evaluate(mixed $output, array $datasetItem): void
    {
        $this->assert(
            new StringContains($datasetItem['reference']),
            $output,
        );
    }
} 
```

The logic is quite straightforward. The evaluator first load the dataset, and then run the evaluation for each item of the dataset.

In the `run` method you can execute your agentic entities with the example input and return the output. The output is then passed to the `evaluate` method where you can performs assetions comparing the output with a reference value or any other logic you want.

### Defining The Dataset

You can use anything you want as dataset. There are no predefined format. The evaluator class simply allows you to load a list of test cases to run the evaluators against them. You have two dataset reader.

#### ArrayDataset

```php
class AgentEvaluator extends BaseEvaluator
{
    public function getDataset(): DatasetInterface
    {
        return new ArrayDataset([
            [
                'input' => 'Hi',
                'reference' => 'help'
            ]
        ]);
    }
    
    ...
}
```

#### JsonDataset

```php
class AgentEvaluator extends BaseEvaluator
{
    public function getDataset(): DatasetInterface
    {
        return new JsonDataset(__DIR__ . '/datasets/dataset.json');
    }
    
    ...
}
```

### Running Evaluations

If you have properly configured your composer file you can use the Neuron CLI to launch the evaluators:

```bash
vendor/bin/neuron evaluations --path=evaluators
```

### Output Interfaces

The evaluation module uses a PHP configuration file to control how evaluation results are displayed. The config system supports multiple output drivers, enabling results to be sent to console, files, databases, or external APIs simultaneously.

#### **Config File**

Create the `evaluation.php` file in your project root:

```php
<?php

use NeuronAI\Evaluation\OutputDrivers\ConsoleDriver;
use NeuronAI\Evaluation\OutputDrivers\JsonDriver;

return [
    'output' => [
        // Output results in the console
        ConsoleDriver::class => ['verbose' => true],

        // Save results in a json file
        JsonDriver::class => ['path' => 'evaluation-results.json'],
    ],
];
```

You can declare an array of options for each output class. This configurations will be passed as arguments to the constructor of the output class implementation.

**If no config file exists**, the system defaults to `ConsoleOutputDriver` with standard output.

#### Creating Custom Output

Implement `EvaluationOutputInterface` to create custom output drivers:

```php
namespace App\Neuron\Evaluations;

use NeuronAI\Evaluation\Contracts\EvaluationOutputInterface;
use NeuronAI\Evaluation\Runner\EvaluatorSummary;

class DatabaseOutput implements EvaluationOutputInterface
{
    public function __construct(
        private readonly \PDO $pdo,
        private readonly string $table = 'evaluations'
    ) {}

    public function output(EvaluatorSummary $summary): void
    {
        $stmt = $this->pdo->prepare(
            "INSERT INTO {$this->table} (passed, failed, success_rate, total_time, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW())"
        );
        $stmt->execute([
            $summary->getPassedCount(),
            $summary->getFailedCount(),
            $summary->getSuccessRate(),
            $summary->getTotalExecutionTime(),
        ]);
    }
}
```

Once you have created your output class you can register it in the configuration file, to be used the next time you run the evaluations.

```php
<?php

use NeuronAI\Evaluation\OutputDrivers\ConsoleDriver;
use NeuronAI\Evaluation\OutputDrivers\JsonDriver;

return [
    'output' => [
        // Output results in the console
        ConsoleDriver::class => ['verbose' => true],

        // Save results in a json file
        //JsonDriver::class => ['path' => 'evaluation-results.json'],
        
        // Save results in the database
        DatabaseOutput::class => [
            'pdo' => new \PDO(...),
            'table' => 'evaluations',
        ]
    ],
];
```


# Getting Started

Step by Step guide on how to implement Retrieval-Augmented Generation with Neuron framework.

{% hint style="info" %}

### PREREQUISITES

This guide assumes you are already familiar with the following concepts:

* [Agent](/v2/the-basics/agent)
* [Tool & Function Call](/v2/the-basics/tools)
  {% endhint %}

Retrieval-Augmented Generation (RAG) is the process of providing references to a knowledge base outside of the LLM training data sources before generating a response.&#x20;

Large Language Models (LLMs) are trained on vast volumes of data to be able to generate original output for tasks like answering questions, translating languages, and completing sentences. RAG extends the already powerful capabilities of LLMs to specific domains or an organization's internal knowledge base, all without the need to retrain the model.&#x20;

It is a cost-effective approach to improving LLM output so it remains relevant, accurate, and useful also working on your own private data.

## Why RAG systems are relevant

Building a RAG system is the way to use the powerful LLM capabilities on your own private data. You can create applications capable of accurately answering questions about a company internal documentations. Or chatbot to serve external customers on the internal rules of an organization.

If it's not about the usage of private data, you can think of RAG as a way to provide the latest research, statistics, or news to the generative models.

## How to create a RAG system

Without RAG, the LLM takes the user input and creates a response based on information it was trained on—or what it already knows.&#x20;

With RAG, an information retrieval component is introduced. It utilizes the user input to first pull information from a new data source. The user query and the relevant information retrieved are both given to the LLM. The LLM uses the new knowledge and its training data to create better responses. The following sections provide an overview of the process.

Even if it can appear a little bit complicated, don't worry, this is just to make you aware of the process. Most of these things are automatically managed by a Neuron RAG agent.

There are three most important steps to create a RAG system.

### Process external data

The external data you want to use to augment the default LLM knowledge may exist in various formats like files, database records, or long-form text.

Before being able to submit this data to the LLM you have to convert them into a specific format called "Embeddings".

### Retrieve relevant information

The embeddings you have generated by processing documents and data need to be stored in specific databases able to deal with their format. This database are called "Vector Store".

Vector store are not only able to store this data, bet also to perform a particular form the "similarity search" between the existing data in the database an a query we provide.&#x20;

### Augment the LLM prompt

Next, the RAG agent augments your input (or prompt) by adding the relevant retrieved data in the context based on your query.

You just need to take care of the first step "Process external data", and Neuron gives you the toolkit to make it simple. The other steps are automatically managed by the Neuron RAG agent.

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

## Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls, external data sources, tools, and more. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to start monitoring:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

## Create a RAG Agent

To create a RAG you need to attach some additional components other than the AI provider, such as a `vector store`, and an `embeddings provider`.

First, let's create the RAG class:

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

```bash
./vendor/bin/neuron make:rag App\\Neuron\\MyChatBot
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:rag App\Neuron\MyChatBot
```

{% endtab %}
{% endtabs %}

Here is an example of a RAG implementation:

```php
namespace App\Neuron;

use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAIEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL'
        );
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(
            directory: __DIR__,
            name: 'demo'
        );
    }
}
```

{% hint style="warning" %}
Explore [**Data Loaders**](/v2/rag/data-loader) to learn how to populate the vector store with embeddings representing the knowledge you want to integrate as additional knowledge.
{% endhint %}

### Talk to the chat bot

Imagine having previously populated the vector store with the knowledge base you want to connect to the RAG agent, and now you want to ask questions. Check out [**Data Loaders**](/v2/rag/data-loader) to laern about RAG data population.

To start the execution of a RAG you call the `chat()`  method:

```php
use App\Neuron\MyChatBot;
use NeuronAI\Chat\Messages\UserMessage;

$response = MyChatBot::make()->chat(
    new UserMessage('I want to know more about Inspector AI Bug Fix.')
);
    
echo $response->getContent();

// Sure, Inspector AI Bug Fix is an agentic monitoring tool 
// that provides bug fix proposals in real-time as an error occurs 
// in your application.
```

## Feed Your RAG With Documents

Once you have defined the components of your RAG system it's time to feed the vector database with embedded chunks of text.

Neuron provides you with [Data Loaders](/v2/rag/data-loader) to help you set up a data loading pipeline with just a few lines of code. You can see an example below. To learn more about data loader you should check out the [dedicated documentation](/v2/rag/data-loader):&#x20;

```php
use App\Neuron\MyChatBot;
use NeuronAI\RAG\DataLoader\FileDataLoader;

MyChatBot::make()->addDocuments(
    // Use the file data loader component to load a text file into the vector store
    FileDataLoader::for(__DIR__.'/my-article.md')->getDocuments()
);
```

## RAG + Tools

The Neuron's RAG class extends the basic `\NeuronAI\Agent` class. This means that your RAG is always an agent and you can also attach tools and define system instructions in your implementation.&#x20;

Imagine we want to implement an agent able to give workout tips based on the user data. Here is an example of a complete implementation:

```php
namespace App\Neuron;

use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;
use NeuronAI\Tools\Toolkits\Calculator\CalculatorToolkit;

class WorkoutTipsAgent extends RAG
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are an AI Agent specialized in providing workout tips."],
        );
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAIEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL'
        );
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(
            directory: __DIR__,
            name: 'demo'
        );
    }
    
    protected function tools(): array
    {
        return [
            CalculatorToolkit::make(),
        ];
    }
}
```

In the example above we created a RAG agent that is able to give workout tips to the user. We can load into the vector store the knowledge for the specific workouts you provide, so the agent has the knowledge to provide tips based on the current workout status of the user retrieved from the database with the tool we attached.


# Data loader

Learn how to create data loader pipelines to feed your RAG applications.

{% hint style="info" %}
PREREQUISITES

This guide assumes you are already familiar with RAG. Check out the dedicated documentation: <https://docs.neuron-ai.dev/rag>
{% endhint %}

To build a structured AI application you need the ability to convert all the information you have into text, so you can generate embeddings, save them into a vector store, and then feed your Agent to answer the user's questions.

<figure><img src="/files/gXvl8JZ77R5GokTBMDBB" alt=""><figcaption></figcaption></figure>

Neuron gives you several tools (data loaders) to simplify this process.&#x20;

```php
use App\Neuron\MyRAG;
use NeuronAI\RAG\DataLoader\FileDataLoader;

MyRAG::make()->addDocuments(
    // Use the file data loader component to process a text file
    FileDataLoader::for(__DIR__.'/my-article.md')->getDocuments()
);
```

Using the Neuron toolkit you can create data loading pipelines with the benefits of unified interfaces to facilitate interactions between components, like embedding providers, vector store, and file readers.

## FileDataLoader

If you need to extract text from files the `FileDataLoader` allows you to process any simple text document.&#x20;

```php
use NeuronAI\RAG\DataLoader\FileDataLoader;

// Read a file and get "documents"
$documents = FileDataLoader::for(__DIR__.'/my-article.md')->getDocuments();

// Pass a directory to process all files
$documents = FileDataLoader::for(__DIR__)->getDocuments();
```

By default `FileDataLoader` read the content of a file as it is in the file system, but not all file type are ready to be treated as simple text. Neuron provides you with the ReaderInterface and several pre-defined reader components for the most common file formats.

Notice that each file reader is associated to a file extension. So based on the input file extension the data loader will automatically use the appropriate reader.

### PDF Reader

{% hint style="warning" %}
To use `PdfReader` you need to install the [**poppler**](https://en.wikipedia.org/wiki/Pdftotext) utility.
{% endhint %}

```php
use NeuronAI\RAG\DataLoader\FileDataLoader;

// Register the PDF reader
$documents = FileDataLoader::for(__DIR__)
    ->addReader('pdf', new \NeuronAI\RAG\DataLoader\PdfReader())
    ->getDocuments();
```

### HTML to Markdown Reader

{% hint style="warning" %}
To use `HtmlReader` you need to install the [**html2text**](https://github.com/mtibben/html2text) composer package.
{% endhint %}

```php
use NeuronAI\RAG\DataLoader\FileDataLoader;

// Register the HTML reader
$documents = FileDataLoader::for(__DIR__)
    ->addReader(['html', 'xhtml'], new \NeuronAI\RAG\DataLoader\HtmlReader())
    ->getDocuments();
```

### StringDataLoader

If you are already getting text from your database or other sources, you can use the StringDataLoader to convert this text into documents, ready to be embedded and stored by the other Neuron components in the chain:

```php
use App\Neuron\MyRAG;
use NeuronAI\RAG\DataLoader\StringDataLoader;

$contents = [
    // list of strings (text you want to embed)
];

foreach ($contents as $text) {
    $documents = StringDataLoader::for($text)->getDocuments(); 
    
    MyRAG::make()->addDocuments($documents);
}
```

### Document meta-data

After getting the array of documents from a data loader you can eventually attach custom meta-data to the document that will be saved in the vector store along with other document default fields:

```php
$documents = FileDataLoader::for($directory)->getDocuments(); 

foreach($documents as $document) {
    $document->addMetadata('user_id', 1234);
}

MyRAG::make()->addDocuments($documents);
```

Once you have these custom fields in the vector store you can use hybrid search for databases that support this feature.&#x20;

{% hint style="info" %}
Hybrid search allows you to narrow the scope of a semantic search query against records that match certain criteria on other document fields rather that compare only the vector embeddings. Explore the [Vector Store section](/v2/rag/vector-store) to know which database support hybrid search.
{% endhint %}

## Text Splitter

Neuron data loaders get files or text in input and generate an array of `\NeuronAI\RAG\Document` objects. These documents are embeddable units. The original text is split into smaller pieces of text to be converted into embeddings and saved in the vector store.

The logic data loaders use to split a long text into chunks can be customized using different strategies. Neuron has a dedicated component for this purpose called "Splitter", and it can be attached to the data loader based on the strategy you prefer or need:

```php
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new DelimiterTextSplitter()
    )
    ->getDocuments();
```

### &#x20;DelimiterTextSplitter (default)

This is the default splitter for all data loaders.

```php
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new DelimiterTextSplitter(
            maxLength: 1000,
            separator: '.',
            wordOverlap: 0
        )
    )
    ->getDocuments();
```

Each of these parameters has an impact on the performance and accuracy of your RAG agent.

#### Max Length

Each chunk will not be longer than this value, and it will be divided into smaller documents eventually. The length can impact the accuracy of embeddings representations. The longer your units of text are, the less accurate the embeddings representation will be.

#### Separator

The text is first split into chunks based on a separator. By default the component uses the period character. You can eventually customize this separator by using any delimiter for your text.

#### Overlap

Sometimes it could be useful to bring words from the previous and next chunk into a document to increase the semantic connection between adjacent sections of the text. By default no overlap is applied.

### SentenceTextSplitter

Splits text into sentences, groups into word-based chunks, and optionally applies overlap in terms of words.

```php
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new SentenceTextSplitter(
            maxWords: 200,
            overlapWords: 0
        )
    )
    ->getDocuments();
```

**MaxWords**: maximum number of words per chunk

**OverlapWords**: number of overlapping words between chunks

### Implement Custom Splitters

You can implement a custom splitting logic implementing the `SplitterInterface`:

```php
namespace NeuronAI\RAG\Splitter;

use NeuronAI\RAG\Document;

interface SplitterInterface
{
    /**
     * @return Document[]
     */
    public function splitDocument(Document $document): array;

    /**
     * @param  Document[]  $documents
     * @return Document[]
     */
    public function splitDocuments(array $documents): array;
}
```

You can interact with external service or create your custom logic to split a long text into smaller chunks. Once you have created your custom implementation you can use it in with the data loaders:

```php
class CustomSplitter implements SplitterInterface
{
    public function splitDocument(Document $document): array
    {
        // Your logic here...
    }
    
    public function splitDocuments(array $documents): array
    {
        // Your logic here...
    }
}

// Use the custom splitter into the data loader pipeline
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new CustomSplitter()
    )
    ->getDocuments();
```

## Reindex Knowledge Source

Reindexing is a hot topic in RAG system design because the practice of breaking text into chunks makes it difficult to update individual pieces of information when the content of the original knowledge changes.

In Neuron The `Document` class is designed to carry some metadata to help you identify the source of each piece of knowledge stored into the vector database, like `sourceType` and `sourceName` fields. Using this information you can easily update the vector store with the updated version of the content from a file previously used as a source of knowledge.

{% hint style="warning" %}
The new version of the file **must have the same path and name** you used originally, otherwise the documents will be added as new ones.
{% endhint %}

```php
$documents = FileDataLoader::for("/path/to/directory")
    ->withSplitter(
        new SentenceTextSplitter(
            maxWords: 200,
            overlapWords: 0
        )
    )
    ->getDocuments();

// Reindex by sourceType and sourceName
MyRAG::make()->reindexBySource($documents);
```

If `sourceType` and `sourceName` of the Documents are already present into the vector store, they will be deleted and the Documents of the new version will be saved. Other documents will be stored as usual into the vector database.

## Use standalone components

In the examples below we used the RAG agent instance to process the final part of the ingestion pipeline: generate embeddings for document chunks, and store them into jthe vector database.&#x20;

In alternative of take advantage of the RAG agent instance you can use the embedding provider and the vector store as standalone components. Remember that the vector store here must be same connected to the RAG agent.

```php
use App\Neuron\MyRAG;
use NeuronAI\RAG\DataLoader\FileDataLoader;
use NeuronAI\RAG\DataLoader\StringDataLoader;
use NeuronAI\RAG\EmbeddingProvider\OpenAIEmbeddingProvider;
use NeuronAI\RAG\VectorStore\FileVectorStore;

$embedder = new OpenAIEmbeddingProvider(
    key: 'OPENAI_API_KEY',
    model: 'OPENAI_MODEL'
);

$store = new FileVectoreStore(
    directory: __DIR__,
    key: 'demo'
);

// Process files and contents
$documents = FileDataLoader::for(__DIR__.'/documents');
    ->addReader('pdf', new \NeuronAI\RAG\DataLoader\PdfReader())
    ->getDocuments(); 

// Generate embeddings and store documents in the vector database
$store->addDocuments(
    $embedder->embedDocuments($documents)
);

```

With this simple process you can ingest GB of data into your vector store to feed your RAG agent.


# Embeddings Provider

Integrate services to transform text into vectors for semantic search.

Transform your text into vector representations! Embeddings let you add Retrieval-Augmented Generation ([RAG](/v2/rag/rag)) into your AI applications.

## Available Embeddings Providers

The framework already includes the following embeddings provider.

### Ollama

With Ollama you can run embedding models locally. Documentation - <https://ollama.com/blog/embedding-models>

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OllamaEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OllamaEmbeddingsProvider(
            model: 'OLLAMA_EMBEDDINGS_MODEL'
        );
    }
}
```

### Voyage AI

Documentation - <https://www.voyageai.com/>

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\VoyageEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new VoyageEmbeddingsProvider(
            key: 'VOYAGE_API_KEY',
            model: 'VOYAGE_EMBEDDINGS_MODEL' // voyage-3-large
        );
    }
}
```

### OpenAI

Documentation - <https://platform.openai.com/docs/guides/embeddings>

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAIEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_EMBEDDINGS_MODEL' // text-embedding-3-small
        );
    }
}
```

### OpenAILikeEmbeddings

You can use any providers comaptible with OpenAI API format:

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAILikeEmbeddings;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAILikeEmbeddings(
            baseUri: 'PRODIDER_URL',
            key: 'PROVIDER_API_KEY',
            model: 'PROVIDER_EMBEDDING_MODEL'
        );
    }
}
```

### Gemini

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\GeminiEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new GeminiEmbeddingsProvider(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_EMBEDDINGS_MODEL' // gemini-embedding-001
        );
    }
}
```

### Aws Bedrock

```php
namespace App\Neuron;

use Aws\BedrockRuntime\BedrockRuntimeClient;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\AwsBedrockEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        $client = new BedrockRuntimeClient([
            'version' => 'latest',
            'region' => 'us-east-1',
            'credentials' => [
                'key' => 'AWS_BEDROCK_KEY',
                'secret' => 'AWS_BEDROCK_SECRET',
            ],
        ]);
        
        return new AwsBedrockEmbeddingsProvider(
            client: $client,
            model: 'AWS_EMBEDDINGS_MODEL'
        );
    }
}
```

## Implement a new Provider

To create a custom provider you just have to extend the `AbstractEmbeddingsProvider` class. This class already implement the framework specific methods and let's you free to implement the only provider specific HTTP call into the `embedText()` method:

```php
namespace App\Neuron\Embeddings;

use GuzzleHttp\Client;

class CustomEmbeddingsProvider extends AbstractEmbeddingsProvider
{
    protected Client $client;

    protected string $baseUri = 'HTTP-ENDPOINT';

    public function __construct(
        protected string $key,
        protected string $model
    ) {
        $this->client = new Client([
            'base_uri' => trim($this->baseUri, '/').'/',
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer ' . $this->key,
            ]
        ]);
    }

    public function embedText(string $text): array
    {
        $response = $this->client->post('', [
            'json' => [
                'model' => $this->model,
                'input' => $text,
            ]
        ]);

        $response = \json_decode($response->getBody()->getContents(), true);

        return $response['data'][0]['embedding'];
    }
}
```

You should adjust the HTTP request based on the APIs of the custom provider.


# Vector Store

Neuron provides you with ready to use components to connect your agent to vector databases.

We currently offer first-party support for the following vector store:

### Memory Vector Store

This is an implementation of a volatile vector store that keeps your embeddings into the machine memory for the current session. It's useful when you don't need to store the generated embeddings for long term use, but just during current interaction sessions (or for local use).

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\MemoryVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new MemoryVectorStore();
    }
}
```

### File Vector Store

File storage could be useful for low volume use case or local and staging environments. Embedded documents will be stored in the file system and processed during similarity search.

`FileVectorStore` uses PHP generators to read the embedded documents from the file systems. It will never keep more than `topK` items in memory while iterating very fast. You can store thousands of documents in your local filesystem only taking care on the maximum time you can accept to perform the similarity search.

You can also use this component to release agents with some knowledge already incorporated in a file.

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(
            directory: storage_path(),
            topK: 4
        );
    }
}
```

### Pinecone

Pinecone makes it easy to provide long-term memory for high-performance AI applications. It’s a managed, cloud-native vector database with a simple API and no infrastructure hassles. Pinecone serves fresh, filtered query results with low latency at the scale of billions of vectors.

Here is how to use Pinecone in your agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\PineconeVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new PineconeVectorStore(
            key: 'PINECONE_API_KEY',
            indexUrl: 'PINECONE_INDEX_URL'
        );
    }
}
```

Pinecone also supports hybrid search that allows you to filter documents not only by similarity with the input prompt, but also by metadata stored along with your documents. You can pass additional filters to your agent instance so Pinecone will take them in consideration while filtering documents.

You can add the `addVectorStoreFilters()` method to your agent class to pass down filters at runtime:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\PineconeVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    protected array $vectorStoreFilters = [];

    ...

    protected function vectorStore(): VectorStoreInterface
    {
        $store = new PineconeVectorStore(
            key: 'PINECONE_API_KEY',
            indexUrl: 'PINECONE_INDEX_URL'
        );

        return $store->withFilters($this->vectorStoreFilters);
    }

    public function addVectorStoreFilters(array $filters): self
    {
        $this->vectorStoreFilters = $filters;
        return $this;
    }
}
```

When you run your agent you can pass filters on the fly:

```php
$response = MyRAG::make()
    ->addVectorStoreFilters([
        // Add filters
    ])
    ->chat(new UserMessage(...));
```

Take a look at the Pinecone official documentation to better understand the metadata filters: <https://docs.pinecone.io/reference/api/2025-04/data-plane/query#body-filter>

### Elasticsearch

Elasticsearch's open source vector database offers an efficient way to create, store, and search vector embeddings. To use Elasticseach as a vector store in your agents implementation you have to import the official client:

```bash
composer require elasticsearch/elasticsearch
```

Here is how to create a RAG that uses Elasticsearch:

```php
namespace App\Neuron;

use Elastic\Elasticsearch\ClientBuilder;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\ElasticsearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        $elasticsearch = ClientBuilder::create()
           ->setHosts(['<elasticsearch-endpoint>'])
           ->setApiKey('<api-key>')
           ->build();
       
        return new ElasticsearchVectorStore(
            client: $elasticsearch,
            index: 'neuron-ai'
        );
    }
}
```

Elasticsearch also support hybrid search. You can pass additional filters to your agent instance so Elasticsearch will take them in consideration while filtering documents.

You can add the `addVectorStoreFilters()` method to your agent class to pass down filters at runtime:

```php
namespace App\Neuron;

use Elastic\Elasticsearch\ClientBuilder;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\ElasticsearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    protected array $vectorStoreFilters = [];

    ...

    protected function vectorStore(): VectorStoreInterface
    {
        // Create the client
        $elasticsearch = ClientBuilder::create()
           ->setHosts(['<elasticsearch-endpoint>'])
           ->setApiKey('<api-key>')
           ->build();
        
        // Create the store
        $store = new ElasticsearchVectorStore(
            client: $elasticsearch,
            index: 'neuron-ai'
        );

        // Apply filters
        return $store->withFilter($this->vectorStoreFilters);
    }

    public function addVectorStoreFilters(array $filters): self
    {
        $this->vectorStoreFilters = $filters;
        return $this;
    }
}
```

When you run your agent you can pass filters on the fly:

```php
$response = MyRAG::make()
    ->addVectorStoreFilters([
        // Add filters
    ])
    ->chat(new UserMessage(...));
```

### OpenSearch

Opensearch is the pure open source alternative to Elasticsearch. To use Opensearch in your agents you need to install its official client:

```bash
composer require opensearch-project/opensearch-php
```

Once you have the official client installed in your app you can return an instance of the `OpenSearchVectorStore` in your RAG agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\OpenSearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;
use OpenSearch\GuzzleClientFactory;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        $opensearch = new GuzzleClientFactory()->create([
            'base_uri' => 'http://localhost:9200',
        ]);
        
        return new OpenSearchVectorStore(
            client: $opensearch,
            index: 'neuron-ai',
        );
    }
}
```

### Typesense

[Typesense](https://typesense.org/) is an open source alternative to the options above. To use Typesense in your agents you need to install its official client:

```bash
composer require typesense/typesense-php
```

Once you have the official client installed in your app you can return an instance of the `TypesenseVectorStore` in your RAG agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\TypesenseVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;
use Typesense\Client;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        $typesense = new Client([
            'api_key' => 'TYPESENSE_API_KEY',
            'nodes' => [
                [
                    'host' => 'TYPESENSE_NODE_HOST',
                    'port' => 'TYPESENSE_NODE_PORT',
                    'protocol' => 'TYPESENSE_NODE_PROTOCOL'
                ],
            ]
        ]);
        
        return new TypesenseVectorStore(
            client: $typesense,
            collection: 'neuron-ai',
            vectorDimension: 1024
        );
    }
}
```

### Qdrant

[Qdrant](https://qdrant.tech/) is an open source vector database with strong similarity search capabilities. To use Qdrant in your agents you have to provide a `collectionUrl`. This means you will first need to create a collection on Qdrant with its attributes like: name, similarity search algorithm, vector dimension, etc.

Once you have the collection URL you can attach the `QdrantVectorStore` instance to your agent.

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\QdrantVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new QdrantVectorStore(
            collectionUrl: 'http://localhost:6333/collections/neuron-ai/',
            key: 'QDRANT_API_KEY'
        );
    }
}
```

### ChromaDB

[Chroma](https://trychroma.com/) is an open source database designed to be an AI application data source. To use ChromaDB in your agents you have to provide the name of an internal collection where you want to store the embeddings.

Once you have the collection created on your Chroma instance you can attach the `ChromaVectorStore` instance to the agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\ChromaVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new ChromaVectorStore(
            collection: 'neuron-ai',
            //host: 'http://localhost:8000', <-- This is by default
            topK: 5
        );
    }
}
```

### Meilisearch

[Meilisearch](https://www.meilisearch.com/) is a hybrid search engine, but the Neuron implementation uses it exclusively as a vector store for embeddings and similarity search.

The `indexUid` parameter should be the identifier of a Meilisearch index that you have created and configured. Make sure this index defines a vector field whose dimension matches the embedding size produced by the embedder you are using. The `embedder` value (for example, `default`) must correspond to a named embedder configured in your Neuron setup so that the stored vectors and the index configuration stay aligned. Add the component to your RAG:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\MeilisearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new MeilisearchVectorStore(
            indexUid: 'MEILISEARCH_INDEXUID',
            host: 'http://localhost:8000', // Or use the cloud URL
            key: 'MEILISEARCH_API_KEY',
            embedder: 'default',
            topK: 5
        );
    }
}
```

### Implement custom Vector Stores

If you want to create a new provider you have to implement the `VectorStoreInterface` interface:

```php
namespace NeuronAI\RAG\VectorStore;

use NeuronAI\RAG\Document;

interface VectorStoreInterface
{
    public function addDocument(Document $document): void;

    /**
     * @param  Document[]  $documents
     */
    public function addDocuments(array $documents): void;

    public function deleteBySource(string $sourceName, string $sourceType): void;

    /**
     * Return docs most similar to the embedding.
     *
     * @param  float[]  $embedding
     * @return Document[]
     */
    public function similaritySearch(array $embedding, int $k = 4): iterable;
}
```

There are two different methods for adding a single document or a collection of documents because many databases provide different APIs for these use cases. If the database you want to interact to doesn't handle these requests differently you can implement `addDocument()` as a placeholder.

The similaritySearch should return documents with a similarity score not a similarity distance. If the underlying database returns a distance you can convert it to a score using the utility class `VectorSimilarity`:

```php
namespace App\Neuron\VectorStore;

use NeuronAI\RAG\Document;
use NeuronAI\RAG\VectorSimilarity;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyVectorStore implements VectorStoreInterface
{
    ...


    /**
     * @param float[] $embeddings
     */
    public function similaritySearch(array $embedding): iterable
    {
        $documents = // get documents from the vector store

        return \array_map(function (Document $document) {
            return $document->setScore(
                VectorSimilarity::similarityFromDistance($similarity)
            );
        }, $documents);
    }
}
```

This is the basic template for a new AI provider implementation.

```php
namespace App\Neuron\VectorStore;

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use NeuronAI\RAG\Document;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyVectorStore implements VectorStoreInterface
{
    protected Client $client;

    public function __construct(
        string $key,
        protected string $index,
        protected int $topK = 5
    ) {
        $this->client = new Client([
            'base_uri' => 'https://api.vector-store.com',
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'Authorization' => "Bearer {$key}",
            ]
        ]);
    }

    public function addDocument(Document $document): void
    {
        $this->addDocuments([$document]);
    }

    /**
     * @param Document[] $documents
     */
    public function addDocuments(array $documents): void
    {
        $this->client->post("indexes/{$this->index}", [
            RequestOptions::JSON => \array_map(function (Document $document) {
                return [
                    'vector' => $document->embedding,
                ];
            }, $documents)
        ]);
    }

    /**
     * @return Document[]
     */
    public function similaritySearch(array $embedding): iterable
    {
        // perform similarity search and return an array of Document objects
    }
}
```

After creating your own implementation you can use it in the agent:

```php
namespace App\Neuron;

use App\Neuron\VectorStore\MyVectorStore;
use NeuronAI\Agent;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyAgent extends Agent
{
    protected function vectorStore(): VectorStoreInterface
    {
        return new MyVectorStore(
            key: 'VECTORSTORE_API_KEY',
            index: 'neuron-ai',
        );
    }
}
```

{% hint style="warning" %}
We strongly recommend you to submit new vector store implementations via PR on the official repository or using other [Inspector.dev](https://inspector.dev/developer-support/) support channels. The new implementation can receives an important boost in its advancement by the community.
{% endhint %}


# Pre/Post Processor

Improve the RAG output by pre/post processing prompts and retrieval results.

As with most software systems, RAG is easy to use but hard to master. The truth is that there is more to RAG than putting documents into a vector DB and adding an LLM on top. That *can work*, but it won't always.

With RAG, you are performing a *semantic search* across many text documents — these could be tens of thousands up to tens of billions of documents.

To ensure fast search times at scale, we typically use vector search — that is, we transform our text into vectors, place them all into a vector database, and compare their proximity to a query using a similarity algorithm (like cosine similarity).

To achieve high quality responses from the RAG agent you can work on two parts of the retrieval process:&#x20;

1. Optimize the user prompt (*Pre-Processors*)
2. Refine the search results gathered from the vector store (*Post-Processors*)

## Pre-Processors

Rather than treating the user's original query as the final word, the pre-processor views it as the starting point for a more sophisticated interaction with the underlying knowledge system. This isn't about second-guessing the user's intent, but about recognizing that their natural language expression often contains multiple embedded questions, implicit constraints, and contextual assumptions that need to be unpacked and reformulated to maximize retrieval effectiveness.

Consider the complexity hidden within seemingly simple queries. When someone asks "Why did our sales drop last quarter?", they're actually expressing a multi-faceted information need that might require understanding seasonal trends, competitor activities, marketing campaign effectiveness, product performance metrics, and economic indicators. A naive RAG system might retrieve general information about sales analysis, missing the opportunity to provide comprehensive, contextually relevant insights that address the full scope of the underlying question.

### Query Transformation

The core of this pattern is to use an LLM to transform the original question into a more structured prompt that the main RAG agent can use to perform a more accurate and effective document retrieval from the vector store.&#x20;

Working with Neuron you can pass the instance of the AI provider already attached to your agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PreProcessor\QueryTransformationPreProcessor;
use NeuronAI\RAG\PreProcessor\QueryTransformationType;

class MyChatBot extends RAG
{
    ...

    protected function preProcessors(): array
    {
        return [
            new QueryTransformationPreProcessor(
                provider: $this->resolveProvider(),
                transformation: QueryTransformationType::REWRITING,
            ),
        ];
    }
}
```

Or use a different provider among the supported AI providers like Gemini, Ollama, OpenAI, HuggingFace, etc.

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PreProcessor\QueryTransformationPreProcessor;
use NeuronAI\RAG\PreProcessor\QueryTransformationType;

class MyChatBot extends RAG
{
    ...

    protected function preProcessors(): array
    {
        return [
            new QueryTransformationPreProcessor(
                // Use one of the supported AI Provider
                provider: new Anthropic(
                    key: 'ANTHROPIC_API_KEY',
                    model: 'ANTHROPIC_MODEL',
                ),
                transformation: QueryTransformationType::REWRITING,
            ),
        ];
    }
}
```

The three core strategies implemented in the Neuron pre-processor are: rewriting, decomposition, and HyDE (Hypothetical Document Embeddings), each tackle different aspects of this query transformation challenge.

**Query rewriting** addresses the fundamental mismatch between conversational language and search-optimized formulations. When users express their needs in casual, context-dependent language, the rewriting process translates these expressions into more precise, searchable formulations that better align with how information is typically organized and indexed.

**Decomposition** handles the reality that complex questions often contain multiple distinct information needs that would be better served by separate retrieval operations. Rather than forcing a single search to satisfy multiple different aspects of a query, decomposition breaks down complex questions into their constituent parts, allowing each component to be addressed with focused precision before synthesizing the results into a comprehensive response.

T**he HyDE approach** represents perhaps the most sophisticated strategy, working backwards from the assumption that the best way to find relevant information is to first imagine what that information might look like. Instead of searching directly with the user's question, HyDE generates hypothetical documents that would ideally answer the query, then uses these generated documents as the basis for similarity searches. This approach is particularly powerful when dealing with abstract concepts or when the user's terminology doesn't closely match the vocabulary used in the source documents.

## Post-Processors&#x20;

For vector search to work instead, we need vectors. These vectors are essentially compressions of the "meaning" behind some text into (typically) 768 or 1536-dimensional vectors. There is some information loss because we're compressing this information into a single vector.

Because of this information loss, we often see that the top three (for example) vector search documents will miss relevant information. Unfortunately, the retrieval may return relevant information below our `top_k` cutoff.

What do we do if relevant information at a lower position would help our LLM formulate a better response? The easiest approach is to increase the number of documents we're returning (increase `top_k`) and pass them all to the LLM.

Unfortunately, we cannot pass everything to the LLM because this dramatically reduces the LLM's performance to find relevant information from the text placed within its context window.

The solution to this issue is retrieving plenty of documents from the vector store and then *minimizing* the number of documents that make it to the LLM. To do that, you can reorder and filter retrieved documents to keep just the most relevant for our LLM.

Neuron allows you to define a list of post-processor components to pipe as many transformations you need to optimize the agent output.

### Rerankers

Reranking is one of the most popular post-process operations you can apply to the retrieved documents. A reranking service calculates a similarity score of each documents retrieved from the vector store with the input query.&#x20;

We use this score to reorder the documents by relevance and take only the most useful.

### Jina Reranker

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PostProcessor\JinaRerankerPostProcessor;
use NeuronAI\RAG\VectorStore\FileVectoreStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectoreStore(
            directory: storage_path(),
            topK: 50
        );
    }

    protected function postProcessors(): array
    {
        return [
            new JinaRerankerPostProcessor(
                key: 'JINA_API_KEY',
                model: 'JINA_MODEL',
                topN: 5
            ),
        ];
    }
}
```

In the example above you can see how the vector store is instructed to get 50 documents, and the reranker will basically take only the 5 most relevant ones.

### Cohere Reranker

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PostProcessor\CohereRerankerPostProcessor;
use NeuronAI\RAG\VectorStore\FileVectoreStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectoreStore(
            directory: storage_path(),
            topK: 50
        );
    }

    protected function postProcessors(): array
    {
        return [
            new CohereRerankerPostProcessor(
                key: 'COHERE_API_KEY',
                model: 'COHERE_MODEL',
                topN: 3
            ),
        ];
    }
}
```

### Fixed Threshold

It uses a simple, configurable fixed threshold to filter documents. Documents with scores below the threshold are removed from results.

It's ideal for scenarios requiring an explicit score cutoff for fixed quality requirements.

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\FixedThresholdPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new FixedThresholdPostProcessor(
                threshold: 0.5
            ),
        ];
    }
}
```

### Adaptive Threshold

It implements a dynamic thresholding algorithm using median and MAD (Median Absolute Deviation). It automatically adjusts to score distributions, making it robust against outliers.

You can configure a multiplier parameter that controls filtering aggressiveness.

Recommended multiplier values:

* \[0.2 to 0.4] High precision mode. For more targeted results with fewer but more relevant documents.
* \[0.5 to 0.7] Balanced mode. Recommended setting for general use cases.
* \[0.8 to 1.0] High recall mode. For more inclusive results that prioritize coverage.
* \>1.0 Not recommended as it tends to include almost all documents.

This component is ideal for cleaning up RAG results with dynamic filtering that adapts to the current result set's score distribution.

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\AdaptiveThresholdPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new AdaptiveThresholdPostProcessor(
                multiplier: 0.6
            ),
        ];
    }
}
```

### LocalAI Reranker

[LocalAI](https://localai.io/) is an all-in-one complete AI stack. You can run large language models locally on your hardware. It provides an OpenAI compatible API for LLMs, so you can use it with the [OpenAILike](/v2/the-basics/ai-provider#openailike) provider.

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\LocalAIPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new LocalAIPostProcessor(
                key: 'LOCALAI_KEY',
                model: 'LOCALAI_MODEL',
                topN: 3,
                host: 'LOCALAI_HOST' // "http://localhost:8080" by default
            ),
        ];
    }
}
```

## Monitoring

Neuron built-in observability features automatically trace the execution of each post processor, so you'll be able to monitor interactions with external services in your [Inspector](https://inspector.dev/) account. Learn more in the [monitoring section](/v2/the-basics/observability).

<figure><img src="/files/DfF2NyneTmLVSDSFnVvC" alt=""><figcaption></figcaption></figure>

## Extending The Framework

With Neuron you can easily create your custom post processor components by simply extending the `\NeuronAI\PostProcessor\PostProcessorInterface`:

```php
namespace NeuronAI\RAG\PostProcessor;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\RAG\Document;

interface PostProcessorInterface
{
    /**
     * Process an array of documents and return the processed documents.
     *
     * @param Message $question The question to process the documents for.
     * @param array<Document> $documents The documents to process.
     * @return array<Document> The processed documents.
     */
    public function process(Message $question, array $documents): array;
}
```

Implementing the `process` method you can perform actions on the list of documents and return the new list. Neuron will run the post processors in the same order they are listed in the `postProcessors()` method.

Here is a practical example:

```php
namespace App\Neuron\PostProcessors;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\RAG\PostProcessor\PostProcessorInterface;

// Implement your custom component
class CutOffPostProcessor implements PostProcessorInterface
{
    public function __constructor(protected int $level) {}

    public function process(Message $question, array $documents): array
    {
        /*
         * Apply a cut off on the score returned by the vector store
         */
         
        return $documents;
    }
}
```


# Retrieval

Implement custom retrieval strategies

### Introduction

The RAG module has a separate retrieval component that allows you to implement different strategies to accomplish context retrieval from external data sources. By default RAG uses `SimilarityRetrieval` that simply query the vector store to retrieve documents similar to the input message:

```php
namespace App\Neuron;

use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\RAG\Retrieval\RetrievalInterface;
use NeuronAI\RAG\RAG\Retrieval\SimilarityRetrieval;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class WorkoutTipsAgent extends RAG
{
    protected function retrieval(): RetrievalInterface
    {
        return new SimilarityRetrieval(
            $this->resolveVectorStore(),
            $this->resolveEmbeddingsProvider()
        );
    }
    
    protected function provider(): AIProviderInterface
    {
        // Return an instance of an AI provider...
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        // Return an embeddings provider instance...
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        // Return a vector store instance...
    }
}
```

Implementing `RetrievalInterface` you are free to create any custom retrieval behaviour for your RAG.

```php
interface RetrievalInterface
{
    /**
     * Retrieve relevant documents for the given query.
     *
     * @return Document[]
     */
    public function retrieve(Message $query): array;
}
```

If you are implementing custom workflow you can use retrieval as a standalone component to dynamically retrieve context data for use in your agentic systems.

### RAPTOR Retrieval Module

Most retrieval-augmented models work by breaking down documents into small chunks and retrieving only the most relevant ones. However, this approach has some limitations:

* **Loss of Context**: Retrieving only small, isolated chunks may miss the bigger picture especially for documents with long contexts.
* **Difficulty in Multi-Step Reasoning**: Some questions require information from multiple sections of a document.

**Use RAPTOR when:**

* Users ask open-ended questions that require comprehensive coverage
* Your domain involves complex topics where context matters as much as facts
* You need to handle queries about themes, trends, or relationships across documents

**Stick with traditional RAG when:**

* Users primarily need quick, specific fact retrieval
* Processing speed and token efficiency are critical constraints

Learn more about RAPTOR in the dedicated repository:

{% embed url="<https://github.com/neuron-core/raptor-retrieval>" %}


# Getting Started

Guide, moderate, and control your multi-agent system with human-in-the-loop.

### What is a Workflow

A workflow is an event-driven, node-based way to control the execution flow of an application.

Your application is divided into sections called Nodes which are triggered by Events, and themselves return Events which trigger further nodes. By combining nodes and events, you can create arbitrarily complex flows that encapsulate logic and make your application more maintainable and easier to understand.

A node can be anything from a single line of code to a complex agent. It can have arbitrary inputs and outputs, which are passed around by Events. It's like n8n at code level.

<figure><img src="/files/gHHUOGTOjinGoJXCCUEA" alt=""><figcaption></figcaption></figure>

Workflow allows you to use all the Neuron components like AI providers, embeddings, data loaders, chat history, vector store, etc, as standalone components to create totally customized agentic entities.

Agent and RAG classes represent a ready to use implementation of the most common patterns when it comes to tool calls, retrieval use cases, structured output, etc. Workflow allows you to program your agentic system completely from scratch. Agent and RAG can be used inside a Workflow to complete tasks as any other component if you need to perform AI tasks during workflow execution.

What makes Neuron Workflows special is their **streaming** and **interruption** capabilities. This means your multi agent system can stream updates directly to clients, pause mid-process, ask for human input, wait for feedback, and then continue exactly where it left off – even if that's hours or days later.

### Why Use Workflows Instead of Regular Scripts?

You might be thinking: "This sounds great, but why can't I just write a regular PHP script with some if-statements and functions?" It's a fair question, and one I heard a lot while building Neuron. The answer becomes clear when you consider what happens when your process needs to go vs multiple branches, several loops and intermediate checkponts, streamimng real-time updates to the client, or even pause, wait, and resume.

When you are at the beginning and your use case is yet quite simple you couldn't see the real potential of Workflow, and it's normal. Keep in mind that if things hit the fan, Neuron already has a solution to help you scale.

### Development Benefits

From a developer perspective, Workflows solve several painful problems:

**Model and maintain complex scenario**: With these simple building blocks you will be able to create simple processes with a few steps, up to complex workflows with iterative loops and intermediate checkpoints.

**Human in the Loop**: Seamlessly incorporates human oversight. You can deploy AI in sensitive areas because humans are always in the loop for critical decisions.

**Streaming**: You can send real-time updates to the client during workflow execution.

**Debugging with inspector**: Instead of wondering why your workflow made a particular decision, you can see exactly what's happening on any node.

**User Trust**: When users know a human reviewed important decisions, they're more likely to trust and adopt your AI system.

### Monitoring & Debugging

Before moving into the Workflow creation process, we recommend having the monitoring system in place. It could make the learning curve of how Workflow works much more easier. The best way to monitoring Workflow is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to monitoring Workflow execution:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

<figure><img src="/files/zDYa7yjaRy1p0fS7e8QA" alt=""><figcaption></figcaption></figure>


# Single Step Workflow

How to create the first workflow with a single node

### Create a Workflow

A workflow is usually implemented as a class that inherits from Workflow. The class can define an arbitrary number of nodes, each of which is a class that extens `NeuronAI\Workflow\Node`.

First, let's create the MyWorkflow class:

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

```bash
./vendor/bin/neuron make:workflow App\\Neuron\\MyAgent
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:workflow App\Neuron\MyAgent
```

{% endtab %}
{% endtabs %}

Here is the simplest possible workflow:

```php
namespace App\Neuron;

use NeuronAI\Workflow\Workflow;

class MyAgent extends Workflow
{
    protected function nodes(): array
    {
        return [
            new InitialNode(),
        ];
    }
}
```

Now let's create the node:

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

```bash
./vendor/bin/neuron make:node App\\Neuron\\InitialNode
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:node App\Neuron\InitialNode
```

{% endtab %}
{% endtabs %}

A Node is an invokable class to handle an incoming event, and return another event:

```php
namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\StartEvent;
use NeuronAI\Workflow\StopEvent;
use NeuronAI\Workflow\WorkflowState;

class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): StopEvent
    {
        $state->set('answer', 'Hello World!');
        
        return new StopEvent();
    }
}
```

This will print "Hello World!" to the console:

```php
$finalState = MyWorkflow::make()->start()->getResult();

echo $finalState->get('answer'); // Print Hello World!
```

In this code we:

* Define a class MyWorkflow that inherits from Workflow
* Define a Node implementing the \_\_invoke method
* The step takes an event as input, $event, which is an instance of StartEvent
* The Node adds a value to the state and returns a StopEvent
* We create an instance of MyWorkflow&#x20;
* We start the workflow and get the result
* Print the result in the console

<figure><img src="/files/gHHUOGTOjinGoJXCCUEA" alt=""><figcaption></figcaption></figure>

### Type hint for events

The $event types (e.g. `StartEvent`) guide the Workflow execution. The expected return types of a node determine what node will be triggered next.

Event types are validated at compile time, so you will get an error message if for instance you return an event that is never consumed by another Node.

### Start and Stop events&#xD;

`StartEvent` and `StopEvent` are special events that are used to start and stop a workflow. The node that accepts a StartEvent will be triggered first by when you start the workflow. Returning a StopEvent will end the execution of the workflow and return the final state, even if other nodes remain un-executed.

### Monitoring & Debugging

Before moving into the Workflow creation process, we recommend having the monitoring system in place. It could make the learning curve of how Workflow works much more easier. The best way to monitoring Workflow is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to monitoring Workflow execution:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}


# Multi Step Workflow

Learn how to handle complex execution flow orchestrating the execution of multiple nodes

Multiple steps are created by defining custom events that can be emitted by nodes and trigger other nodes. Let's define a simple 3-step workflow.

### Custom Events

We define two custom events, `FirstEvent` and `SecondEvent`. These classes can have any names and properties, but must implement `Event`:

```php
namespace App\Neuron;

class FirstEvent implements Event 
{
    public function __construct(protected string $firstMsg){}
}

class SecondEvent implements Event 
{
    public function __construct(protected string $secondMsg){}
}
```

### Defining the workflow

Now we define the workflow itself. We do this by defining the input and output types on each node.

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

```bash
./vendor/bin/neuron make:node App\\Neuron\\InitialNode

./vendor/bin/neuron make:node App\\Neuron\\NodeOne

./vendor/bin/neuron make:node App\\Neuron\\NodeTwo
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:node App\Neuron\InitialNode

.\vendor\bin\neuron make:node App\Neuron\NodeOne

.\vendor\bin\neuron make:node App\Neuron\NodeTwo
```

{% endtab %}
{% endtabs %}

Here is the minimal implementation for the purpose of this demo:

```php
namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\StartEvent;
use NeuronAI\Workflow\StopEvent;

// Gets the StartEvent and returns FirstEvent
class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): FirstEvent
    {
        echo "\n- Handling StartEvent";
        
        return new FirstEvent("InitialNode complete");
    }
}

// Takes FirstEvent as input and returns SecondEvent
class NodeOne extends Node
{
    public function __invoke(FirstEvent $event, WorkflowState $state): SecondEvent
    {
        echo "\n- ".$event->firstMsg;
        
        return new SecondEvent("NodeOne complete");
    }
}

// Takes SecondEvent as input and returns StopEvent
class NodeTwo extends Node
{
    public function __invoke(SecondEvent $event, WorkflowState $state): StopEvent
    {
        echo "\n- ".$event->secondMsg;
        
        echo "\n- NodeTwo complete";
        
        return new StopEvent();
    }
}
```

Define the Workflow attaching the nodes:

```php
use NeuronAI\Workflow\Workflow;

$handler = Workflow::make()
    ->addNodes([
        new InitialNode(),
        new NodeOne(),
        new NodeTwo(),
    ])
    ->start();

/*
 * Run the workflow
 */
$handler->getResult();
```

The full output will be:

```
- Handling StartEvent
- InitialNode complete
- NodeOne complete
- NodeTwo complete
```

<figure><img src="/files/69WmObamPhcKyXcMxTRI" alt=""><figcaption></figcaption></figure>

Of course there is still not much point to a workflow if you just run through it from beginning to end! Let's do some branching and looping.

### Monitoring & Debugging

Before moving into the Workflow creation process, we recommend having the monitoring system in place. It could make the learning curve of how Workflow works much easier. The best way to monitoring Workflow is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to monitoring Workflow execution:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}


# Loops & Branches

Workflow makes branching and looping logic easy to implement thanks to its event driven design. Once you understand how nodes belong to events, it's easy to start imagining how you can create loops and branching, which is just deciding which event should be returned in an "if condition" or whatever logic.

### Loops

To create a loop, simply return the entry event of a previous node as the exit event of the current node. You can also use the same entry event as the current node's exit event to loop over the current node.

Take a look at the example below. The `NodeOne` can have two events as return type, `FirstEvent` and `SecondEvent`. If the node returns FirstEvent it will cause another execution of the same node because FirstEvent is handled by itself, creating a loop.

If the node returns SecondEvent it will finally move forward the execution to another node.&#x20;

```php
class NodeOne extends Node
{
    public function __invoke(FirstEvent $event, WorkflowState $state): FirstEvent|SecondEvent
    {
        echo "\n- ".$event->firstMsg;
        
        if (rand(0, 1) === 1) {
            // Returning FirstEvent it will trigger another execution of NodeOne
            return new FirstEvent("Running a loop on NodeOne");
        }
        
        return new SecondEvent("NodeOne complete, move forward");
    }
}
```

{% hint style="warning" %}
Notice the node has now two return types for the `__invoke` method: `FirstEvent` and `SecondEvent`. You have to declare all possible return events on the method signature to let the Workflow build the execution chain.
{% endhint %}

Returning FirstEvent will trigger another execution of `NodeOne`. So the final output could be:

```php
$state = Workflow::make()
    ->addNodes([
        new InitialNode(),
        new NodeOne(),
        new NodeTwo()
    ])
    ->start()
    ->getResult();

/*
- Handling StartEvent
- InitialNode complete
- Running a loop on NodeOne
- Running a loop on NodeOne
- NodeOne complete, move forward
- NodeTwo complete
*/
```

You can create a loop from any node to any other node in the workflow by defining the appropriate input event and return events of the invoke method.&#x20;

<figure><img src="/files/u9bJsIF4hPw543M4GVSx" alt=""><figcaption></figcaption></figure>

The `NodeOne` can even return a StartEvent to jump right to the first node of the Workflow. The event driven architecutre allows you to directly point any node in the workflow both forward and backward.

### Branches

As you've already seen, you can conditionally return different events from a node to define custom execution flows. In this section we'll see an example of a workflow that branches into two different paths.&#x20;

First let's create some custom events:

```php
namespace App\Neuron;

class BrancheA1Event implements Event 
{
    public function __construct(protected string $firstMsg){}
}

class BrancheA2Event implements Event 
{
    public function __construct(protected string $secondMsg){}
}

class BrancheB1Event implements Event 
{
    public function __construct(protected string $secondMsg){}
}

class BrancheB2Event implements Event 
{
    public function __construct(protected string $secondMsg){}
}
```

In the initial node of he workflow we decide what branched we want to go through. Remeber to always define the appropriate return types in the `__invoke` method signature:

```php
class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): BrancheA1Event|BrancheB1Event
    {
        if (rand(0, 1) === 1) {
            // Returning FirstEvent it will trigger another execution of NodeOne
            return new BrancheA1Event();
        }
        
        return new BrancheB1Event();
    }
}
```

The other nodes will move forward sequencially.

```php
$state = Workflow::make()
    ->addNodes([
        new InitialNode(),
        new A1Node(),
        new A2Node(),
        new B1Node(),
        new B2Node(),
    ])
    ->start()
    ->getResult();
```

<figure><img src="/files/rf5hcUx0UcNZ0g7bf0Fe" alt=""><figcaption></figcaption></figure>

You can of course combine branches and loops in any order to fulfill the needs of your application.&#x20;

### Monitoring & Debugging

Before moving into the Workflow creation process, we recommend having the monitoring system in place. It could make the learning curve of how Workflow works much more easier. The best way to monitoring Workflow is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to monitoring Workflow execution:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}


# Managing the State

Learn how to pass data around the workflow

Generally speaking, the purpose of a workflow is to get an input state, make able the nodes to manipulate this state during execution, and return the state as final value. Based on this architecture the state has two main roles that we will see in detail in this guide.

### Workflow Input/Output

The final return value of the workflow itself is an instance of the workflow state. So, if you need to collect the result of the workflow execution, nodes must be able to write and read from the state until the workflow ends and return the final state to the parent script.

You can also provide an initial state to workflow to feed in input values.

```php
// 1. Provide an initial state as workflow input to feed in some data
$workflow = Workflow::make(new WorkflowState(['query' => 'Hi!']))
        ->addNode(new InitialNode())
        ->addNode(...)
        ->addNode(...);

// 2. Execute the workflow and get the final state
$finalState = $workflow->start()->getReturn();

// 3. Use the final state data
echo $finalState->get('message');
```

### Using state in nodes

In our examples so far, we have passed data from node to node using properties of custom events. This is a powerful way to pass data around, but it has limitations. For example, if you want to pass data between steps that are not directly connected, you need to pass the data through all the nodes in between. This can make your code harder to read and maintain.

For this reasons we have the `WorkflowState` object available to every node in the workflow. To use it, the workflow inject the WorkflowState instance as the second argument of the node.&#x20;

```php
namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\StartEvent;
use NeuronAI\Workflow\StopEvent;
use NeuronAI\Workflow\WorkflowState;

class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): StopEvent
    {
        $state->set('message', 'Hello World!');
        
        return new StopEvent();
    }
}

// Execute the workflow and get the final state
$finalState = Workflow::make()
    ->addNode(new InitialNode())
    ->start()
    ->getReturn();
    
// It will print "Hello World!"
echo $finalState->get('message');
```

### Typed State

The default `WorkflowState` class is just a proxy to an internal array to carry data during workflow execution. It might be useful to create a custom state class to define strictly typed properties for better code completion, validation, and debugging.

Create a `CustomState` class:

```php
use App\Models\User;
use NeuronAI\Workflow\WorkflowState;

class CustomState extends WorkflowState
{
    protected User $user;
    
    public function setUser(User $user): CustomState
    {
        $this->user = $user;
        return $this;
    }
    
    public function getUser(): User
    {
        return $this->user;
    }
}
```

Nodes can accept an instance of `CustomState` instead of the default `WorkflowState`:

```php
class ExampleNode extends Node 
{
    public function __invoke(StartEvent $event, CustomState $state): StopEvent
    {
        // Use state properties in your nodes
        if ($state->getUser()->isAdmin()) {
            //...
        }
        
        return new StopEvent();
    }
}
```

Finally, inject the `CustomState` into the workflow:

```php
$state = new CustomState();
$state->setUser($user);

$workflow = MyWorkflow::make($state);

$finalState = $workflow->start()->getResult();
echo $finalState->getUser()->email;
```


# Human In The Loop

The key breakthrough is that interruption isn't a bug – it's a feature.

Neuron Workflow supports a robust **human-in-the-loop** pattern, enabling human intervention at any point in an automated process. This is especially useful in large language model (LLM)-driven applications where model output may require validation, correction, or additional context to complete the task.

Here's how it works technically:

**Interruption Points**: Any node in your Workflow can request an interruption by specifying the data it want to present to the human. This could be a simple yes/no decision, a content review, data validation, or structured data.

**State Preservation**: When an interruption happens, Neuron automatically saves the complete state of your Workflow. Your Workflow essentially goes to sleep, waiting for human input.

**Resume Capability**: Once a human provides the requested input, the Workflow wakes up exactly from the node it left off. No data is lost, no context is forgotten.

**External Feedback Integration**: The human input is injected into the interrupted node to be consumed on resume.

### Interruption

When a Neuron Workflow encounters an interruption, it doesn't simply stop—it preserves its entire state, and waits for guidance before proceeding. This allows oyu to creates a hybrid intelligence system where AI handles the computational heavy lifting while humans contribute to strategic oversight, domain expertise, and decision-making.

The simplest way to can ask for an interruption is calling the `interrupt()` method inside a node:

```php
<?php

namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Interrupt the workflow and wait for the feedback.
        $feedback = $this->interrupt([
            'question' => 'Should we continue?',
            'current_value' => $state->get('accuracy')
        ]);
    
        if ($feedback['approved']) {
            $state->set('is_sufficient', true);
            $state->set('user_response', $feedback['response']);
            return new OutputEvent();
        }
        
        $state->set('is_sufficient', false);
        return new InputEvent();
    }
}
```

Calling the `interrupt()` method you can pass the information you need to interact with the human. You will be able to catch this data later, outside of the workflow so you can inform the user with relevant information from inside the Workflow to ask for feedback.&#x20;

When the Workflow will be awakened it will restart from this node, and the `$feedback` variable will receive the human's response data.

### Checkpointing

When the Workflow is awakened it restarts the execution from the node where it was interrupted. The node will be re-executed entirely including the code present before the interruption.

If you need to call for an interruption not at the beginning of the node, but after performing other operations, you can use checkpoints to save the result of some statements to be used when the node is re-starded. Here is an example:

```php
<?php

namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // The result of this code block is saved and returned when the workflow is awakened.
        $sentiment = $this->checkpoint('agent-1', function () {
            return MyAgent::make()->structured(
                new UserMessage(...),
                SentimentResult::class
            );
        });
        
        // Interrupt the workflow and wait for the feedback.
        if ($sentiment->isNegative()) {
            $feedback = $this->interrupt([
                'question' => 'Should we continue?',
                'current_value' => $state->get('accuracy')
            ]);
        }
    
        if ($feedback['approved']) {
            $state->set('is_sufficient', true);
            $state->set('user_response', $feedback['response']);
            return new OutputEvent();
        }
        
        $state->set('is_sufficient', false);
        return new InputEvent();
    }
}
```

The checkpoint method accepts two arguments:

* The **name** of the checkpoint must be unique in the node;
* A **Closure** to wrap the code whose result you want to save.

When the node is executed, the checkpoint method saves the result of the Closure in case of an interruption. When the node is executed again after the interruption, it can reach the interruption point with the exact same state of the previous run to get the external feedback.

### Consume The Feedback Directly

You can also consume the external feedback somewhere in your code other than where you call the `interrupt()` method.

The `consumeInterruptFeedabck()` method allows you get the value of the external feedback or null if the node is simply running and not awakening:

```php
<?php

namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Interrupt the workflow and wait for the feedback.
        $feedback = $this->consumeInterruptFeedback();
    
        if ($feedback['approved'] ?? false) {
            $state->set('is_sufficient', true);
            $state->set('user_response', $feedback['response']);
            return new OutputEvent();
        }
        
        $this->interrupt([
            'question' => 'Should we continue?',
            'current_value' => $state->get('accuracy')
        ]);
        
        $state->set('is_sufficient', false);
        return new InputEvent();
    }
}
```

This allows much more flexibility if you need to condition the beginning of the node based on the given feedback.

### Conditional Interruption

You can also use `interruptIf()` as an helper to evaluate a conditional interruption:

```php
<?php

namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Conditional interruption
        $this->interruptIf(
            $state->get('is_sufficient', false), 
            ['question' => 'Should we continue?']
        );
        
        // Using a callback to evaluate the condition
        $this->interruptIf(
            fn() => $state->get('is_sufficient', false), 
            ['question' => 'Should we continue?']
        );
        
        return new InputEvent();
    }
}
```

### Catch the Interruption

To be able to interrupt and wake up a Workflow you need to provide a persistence layer and a workflow ID when creating the Workflow instance:

```php
$workflow = new WorkflowAgent(
    new FilePersistence(__DIR__),
    'CUSTOM_ID'
);
```

The `ID` is the reference to save and load the state of a specific Workflow during the interruption and wake up process. When a node call for an interruption it fires a special type of exception represented by the `WorkflowInterrupt` class. You can catch this exception to manage the interruption request.

```php
try {
    $result = $workflow->start()->getResult();
} catch (WorkflowInterrupt $interrupt) {
    $data = $interrupt->getData();
    
    /*
     * Store $data['question'], $data['current_value'] and the Workflow-ID,
     * and alert the user to provide a feedback.
     */
}
```

Use the information in the `$data` array to guide the human in providing a feedback. Once you finally have the user's feedback you can resume the workflow. Remeber to use the same `ID` of the interrupted execution.

```php
$workflow = new WorkflowAgent(
    new FilePersistence(__DIR__),
    'CUSTOM_ID' // <- Use the same ID of the interrupted workflow
);

// Resume the Workflow passing the human feedback
$result = $workflow-wakeup(['approved' => true])->getResult();

// Get the final answer
echo $result->get('answer');
```

You can take a look at the script below as an example of this process:&#x20;

{% @github-files/github-code-block url="<https://github.com/inspector-apm/neuron-ai/blob/main/examples/workflow/workflow-interrupt.php>" %}

### Monitoring & Debugging

Before moving into the Workflow creation process, we recommend having the monitoring system in place. It could make the learning curve of how Workflow works much more easier. The best way to monitoring Workflow is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to monitoring Workflow execution:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}


# Persistence

Persist the Workflow State across executions.

When we talk about persistence in Neuron, we're talking about the system's ability to capture and preserve the complete state of a running workflow at any moment. This includes:

* **All variables and their current values**&#x20;
* **The exact execution position** – which node is active, which have completed, which are waiting
* **Context and metadata** – timestamps, user information, decision history
* **Error states and retry counters** – so failures can be handled gracefully

Think of it like a sophisticated "save game" feature, but for business processes. At any point, when an interruption is asked from a node, Neuron create a snapshot of your workflow's state and store it in the persistence layer. Later – whether that's seconds, hours, or weeks – the workflow can be restored to exactly that moment and continue as if nothing happened.

As usual in Neuron the Workflow persistence layer is built on top of a common interface so it's extensible and interchangeable. Below the supported persistence layer.

### When to use Persistence

Persistence comes into play when you intend to use interruption. The persistence component requires to decalre also a workflow ID.

### InMemoryPersistence

It keep data in memory only for the current execution cycle.

```php
$workflow = new WorkflowAgent(
    new InMemoryPersistence(), 
    'CUSTOM_ID'
);
```

### FilePersistence

It will store the Workflow data and state into a local file.

```php
$workflow = new WorkflowAgent(
    new FilePersistence(__DIR__), 
    'CUSTOM_ID'
);
```

{% hint style="warning" %}
*FilePersistence* component uses PHP serialization to store the current state of the Workflow. While this allows you to use any PHP object as an item of the Workflow state (e.g. [ChatHistory](/v2/the-basics/chat-history-and-memory)), it also has some limitations like it does not support serialization of Closure. If objects you want to save in the Workflow state conflict with the PHP standard serialization process, you can implement the [Serializable interface](https://www.php.net/manual/en/class.serializable.php) to let the NeuronAI persistence component know of how to serialize the object in the correct way.
{% endhint %}

### DatabasePersistence

To persist the workflow interruption in the database you need to pass a `PDO` instance. If you are working on top of a framework you can get it from the ORM in the same way of the [SQLChatHistory](/v2/the-basics/chat-history-and-memory#sqlchathistory).

```php
use NeuronAI\Workflow\Persistence\DatabasePersistence;

$workflow = new WorkflowAgent(
    new DatabasePersistence(
        pdo: new \PDO(...),
        table: 'workflow_interrupts'
    ), 
    'CUSTOM_ID'
);
```

Here are the SQL scripts to create the table:

{% tabs %}
{% tab title="MySQL/MariaDB" %}

```sql
CREATE TABLE IF NOT EXISTS workflow_interrupts (
    workflow_id VARCHAR(255) PRIMARY KEY,
    data LONGBLOB NOT NULL,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    
    INDEX idx_workflow_id (workflow_id),
    INDEX idx_updated_at (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

{% endtab %}

{% tab title="PostgreSQL" %}

```sql
CREATE TABLE workflow_interrupts (
    workflow_id VARCHAR(255) PRIMARY KEY,
    data BYTEA NOT NULL,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL
);

CREATE INDEX idx_workflow_id ON workflow_interrupts(workflow_id);
CREATE INDEX idx_updated_at ON workflow_interrupts(updated_at);
```

{% endtab %}

{% tab title="SQLite" %}

```sql
CREATE TABLE workflow_interrupts (
    workflow_id TEXT PRIMARY KEY,
    data BLOB NOT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE INDEX idx_workflow_id ON workflow_interrupts(workflow_id);
CREATE INDEX idx_updated_at ON workflow_interrupts(updated_at);
```

{% endtab %}
{% endtabs %}


# Streaming

Stream real -time updates during workflow execution

Workflows can be complex, they are designed to handle complex, branching, itarable logic, which means they can take time to fully execute. To provide your user with a good experience, you may want to provide an indication of progress by streaming events as they occur. Workflows have built-in support for this directly from inside the nodes.

### Emit events from nodes

Let's set up a new event to handle streaming our progress as we go:

```php
namespace App\Neuron;

class ProgressEvent implements Event 
{
    public function __construct(protected string $msg){}
}
```

We'll take our example MyWorkflow with multiple nodes from the previous tutorial and modify the nodes to stream updates instead of echoing output directly:

```php
namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\StartEvent;
use NeuronAI\Workflow\StopEvent;

class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): \Generator|FirstEvent
    {
        yield new ProgressEvent("Handling StartEvent");
        
        return new FirstEvent("InitialNode complete");
    }
}

class NodeOne extends Node
{
    public function __invoke(FirstEvent $event, WorkflowState $state): \Generator|SecondEvent
    {
        yield new ProgressEvent($event->firstMsg);
        
        return new SecondEvent("NodeOne complete");
    }
}

class NodeTwo extends Node
{
    public function __invoke(SecondEvent$event, WorkflowState $state): \Generator|StopEvent
    {
        yield new ProgressEvent($event->secondMsg);
        
        yield new ProgressEvent("NodeTwo complete");
        
        $state->set('message', 'Streaming end');
        
        return new StopEvent();
    }
}
```

{% hint style="warning" %}
To stream events from node you need to add `\Generator` as additional return type of the `__invoke` method.
{% endhint %}

To actually get this output, we need to start the workflow and listen for the events, like this:

```php
$handler = Workflow::make()
    ->addNodes([
        new InitialNode(),
        new NodeOne(),
        new NodeTwo(),
    ])
    ->start();

foreach ($handler->streamEvents() as $event) {
    if ($event instanceof ProgressEvent) {
        echo "\n- ".$event->message;
    }
}

$finalState = $handler->getResult();

// It will print "Streaming end"
echo "\n- ".$finalState->get('message');
```

The full output will be:

```
- Handling StartEvent
- InitialNode complete
- NodeOne complete
- NodeTwo complete
- Streaming end
```

### Stream Agent Output

Running Agents inside nodes is one of the most common use case working with workflow. You may be interested in directly stream the agent output to the client to give real time feedback of the underlying generation. Workflow allows you to do this by simply streaming the agent's output from within the node.

```php
class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): \Generator|FirstEvent
    {
        // Run an agent with streaming
        $stream = Agent::make()->stream(new UserMessage($state->get('prompt')));

        foreach ($stream as $text) {
            yield new GenerationProgressEvent($text);
        }
        
        return new FirstEvent("InitialNode complete");
    }
}
```

To get this output you can listen for workflow events as usual:

```php
$handler = Workflow::make()
    ->addNodes([
        new InitialNode(),
        new NodeOne(),
        new NodeTwo(),
    ])
    ->start();

foreach ($handler->streamEvents() as $event) {
    echo match(getClass($event)) {
        ProgressEvent::class => "\n- ".$event->message,
        GenerationProgressEvent::class => $event->text,
        default => ''
    }
}
```

### Monitoring & Debugging

Before moving into the Workflow creation process, we recommend having the monitoring system in place. It could make the learning curve of how Workflow works much more easier. The best way to monitoring Workflow is with [Inspector](https://inspector.dev/).

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file to monitoring Workflow execution:

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}


# Examples

Learn about the Workflow features through real code examples

## Deep Research Agent

This project is inspired by Open Deep Research, which uses LangGraph for implementation. Other implementations exist also for llamaindex, and others. Our version leverages Neuron to create a powerful, modular workflow for research and analysis.

Neuron Open Deep Research provides a structured approach to generating comprehensive research reports on any topic using large language models, with a focus on modularity, extensibility, and real-time results.

<figure><img src="/files/Gsk4Iz7sO4iyXMl1TlO8" alt=""><figcaption><p><a href="https://github.com/neuron-core/deep-research-agent">https://github.com/neuron-core/deep-research-agent</a></p></figcaption></figure>

### Architecture

**DeepResearchAgent**: Orchestrates the overall report generation process

* **Planning**: Creates the structure of the report
* **GenerateSectionContent**: Generates content for each section using search results
* **Format**: Compiles the final report

**SearchWorkflow**: Handles search operations as a nested workflow

* **GenerateQueries**: Creates search queries based on section topics
* **SearchTheWeb**: Executes parallel searches and processes results

<a href="https://github.com/neuron-core/deep-research-agent" class="button secondary" data-icon="github">Check out the GitHub repository</a>

## Travel Planner Agent

This project demonstrates how to create a tour planner using Neuron PHP framework for agentic applications.

Stack Used:

* Neuron Workflow for multi-agent orchestration.
* [SerpAPI](https://serpapi.com/) for finding hotels, flights and places to visit comprehensive research reports on any topic using large language models, with a focus on modularity, extensibility, and real-time results.

<figure><img src="/files/WfZw7cS3yr2dmJYF0RqI" alt=""><figcaption><p><a href="https://github.com/neuron-core/travel-planner-agent">https://github.com/neuron-core/travel-planner-agent</a></p></figcaption></figure>

### Architecture

**TravelPlannerAgent**: Orchestrates the overall itinerary generation process

#### Nodes

* **Receptionist**: Collect all the information from the user
* **Delegator**: Generates single reports for flights, hotels, and places to visit
  * *Flights*
  * *Hotels*
  * *Places*
* **GenerateItinerary**: Generates the final report

<a href="https://github.com/neuron-core/travel-planner-agent" class="button secondary" data-icon="github">Check out the GitHub Repository</a>

## Laravel Travel Agent

This project demonstrates how to integrate multi-agent workflows in a Laravel application using Neuron PHP AI framework.&#x20;

Stack Used:

* [Laravel](https://laravel.com/) and [Livewire](https://livewire.laravel.com/) for the application.
* [Neuron Workflow](https://docs.neuron-ai.dev/workflow/getting-started) for multi-agent orchestration.
* [SerpAPI](https://serpapi.com/) for finding hotels, flights and places to visit comprehensive research reports on any topic using large language models, with a focus on modularity, extensibility, and real-time results.

<figure><img src="/files/2tRw8eZPhkZxW3Ms4i2i" alt=""><figcaption></figcaption></figure>

### How to use this project

Download the project on your machine and open your terminal in the project directory. First, install the composer dependencies:

```bash
composer install

npm run build

php artisan migrate
```

Create a `.env` file in your project root (see `.env.example` for a template), and provides the API keys based on the service you want to connect with.

```
# At least one required
ANTHROPIC_API_KEY=
GEMINI_API_KEY=
OPENAI_API_KEY=

#Required
SERPAPI_KEY=

# Optional
INSPECTOR_INGESTION_KEY=
INSPECTOR_TRANSPORT=sync
```

Open the project in your browser, register an account, and start planning your trip.


# Testing

Fake components to help you test your AI powered system

When you test an agent, you don't want every test run to make real API calls to OpenAI, Anthropic, or any other provider. Real calls are slow, cost money, and return different results every time, making your tests flaky and expensive. The same applies to RAG agents: you don't want to spin up a vector database or call an embeddings API just to verify your agent's logic.

Neuron ships with drop-in test doubles that solve this problem. `FakeAIProvider` replaces the AI provider, `FakeEmbeddingsProvider` replaces the embeddings provider, and `FakeVectorStore` replaces the vector store. They return predetermined responses, never hit the network, and record every interaction so you can assert exactly what your agent did.

#### Setup <a href="#setup" id="setup"></a>

Create a `FakeAIProvider` with the responses you expect the model to return, then inject it into your agent:

```php
use NeuronAI\Chat\Messages\Stream\AssistantMessage;
use NeuronAI\Testing\FakeAIProvider;

$provider = new FakeAIProvider(
    new AssistantMessage('Hello! How can I help you?')
);

$agent = MyAgent::make()->setAiProvider($provider);
```

Responses are returned in order. If your agent makes multiple calls to the provider (e.g. tool calls), queue multiple responses:

```php
$provider = new FakeAIProvider(
    new AssistantMessage('First response'),
    new AssistantMessage('Second response'),
);
```

#### Chat <a href="#chat" id="chat"></a>

```php
public function test_agent_responds(): void
{
    $provider = new FakeAIProvider(
        new AssistantMessage('The capital of France is Paris.')
    );

    $agent = MyAgent::make()->setAiProvider($provider);

    $message = $agent->chat(new UserMessage('What is the capital of France?'))->getMessage();

    $this->assertSame('The capital of France is Paris.', $message->getContent());
    $provider->assertCallCount(1);
}
```

#### Streaming <a href="#streaming" id="streaming"></a>

The fake provider splits the response text into chunks, simulating a real stream:

```php
public function test_agent_streams_response(): void
{
    $provider = new FakeAIProvider(
        new AssistantMessage('Hello world')
    );

    $agent = MyAgent::make()->setAiProvider($provider);

    $handler = $agent->stream(new UserMessage('Hi'));

    $chunks = [];
    foreach ($handler->events() as $event) {
        if ($event instanceof \NeuronAI\Chat\Messages\Stream\Chunks\TextChunk) {
            $chunks[] = $event->content;
        }
    }

    // The response is split into chunks of 5 characters by default
    $this->assertSame(['Hello', ' worl', 'd'], $chunks);

    // The final message is available after the stream is consumed
    $state = $handler->run();
    $this->assertSame('Hello world', $state->getMessage()->getContent());
}
```

You can change the chunk size with `setStreamChunkSize()`:

```php
$provider->setStreamChunkSize(10);
```

#### Structured Output <a href="#structured-output" id="structured-output"></a>

Provide a JSON string that matches your output class schema. The agent will deserialize and validate it as usual:

```php
public function test_agent_returns_structured_output(): void
{
    $provider = new FakeAIProvider(
        new AssistantMessage('{"name": "Alice"}')
    );

    $agent = MyAgent::make()->setAiProvider($provider);

    $user = $agent->structured(new UserMessage('Generate a user'), User::class);

    $this->assertInstanceOf(User::class, $user);
    $this->assertSame('Alice', $user->name);
}
```

#### Tool Calls <a href="#tool-calls" id="tool-calls"></a>

When the model decides to call a tool, it returns a `ToolCallMessage`. The agent executes the tool and loops back to the provider for a final answer. Queue both responses:

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

public function test_agent_uses_tools(): void
{
    $searchTool = Tool::make('search', 'Search the web')
        ->addProperty(new ToolProperty('query', PropertyType::STRING, 'Search query', true))
        ->setCallable(fn (string $query): string => "Results for: {$query}");

    $provider = new FakeAIProvider(
        // First call: the model asks to use the search tool
        new ToolCallMessage(null, [
            (clone $searchTool)->setCallId('call_1')->setInputs(['query' => 'PHP frameworks']),
        ]),
        // Second call: the model responds using the tool result
        new AssistantMessage('Here are the top PHP frameworks...')
    );

    $agent = MyAgent::make()
        ->setAiProvider($provider)
        ->addTool($searchTool);

    $message = $agent->chat(new UserMessage('Best PHP frameworks?'))->getMessage();

    $this->assertSame('Here are the top PHP frameworks...', $message->getContent());
    $provider->assertCallCount(2);
}
```

#### Assertions <a href="#assertions" id="assertions"></a>

`FakeAIProvider` includes built-in assertions you can use in your tests:

```php
// Verify the total number of provider calls
$provider->assertCallCount(2);

// Verify calls by method
$provider->assertMethodCallCount('chat', 1);
$provider->assertMethodCallCount('stream', 1);

// Verify no calls were made
$provider->assertNothingSent();

// Verify the system prompt
$provider->assertSystemPrompt('You are a helpful assistant.');

// Verify tools were configured
$provider->assertToolsConfigured(['search', 'calculator']);

// Custom assertion with a callback
$provider->assertSent(fn (RequestRecord $record): bool =>
    $record->method === 'chat'
    && $record->messages[0]->getContent() === 'Hello'
);
```

#### Inspecting Requests <a href="#inspecting-requests" id="inspecting-requests"></a>

For more advanced checks, access the raw recorded requests:

```php
$records = $provider->getRecorded();

$records[0]->method;          // 'chat', 'stream', or 'structured'
$records[0]->messages;        // Message[] sent to the provider
$records[0]->systemPrompt;    // The system prompt at call time
$records[0]->tools;           // The tools configured at call time
$records[0]->structuredClass; // The output class (structured calls only)
$records[0]->structuredSchema; // The JSON schema (structured calls only)
```

### RAG <a href="#rag" id="rag"></a>

RAG agents depend on an embeddings provider and a vector store in addition to the AI provider. Neuron provides `FakeEmbeddingsProvider` and `FakeVectorStore` to replace both in tests.

**FakeEmbeddingsProvider**

Generates deterministic embeddings without calling any external API. Drop it in wherever you need an embeddings provider:

```php
use NeuronAI\Testing\FakeEmbeddingsProvider;

$embeddings = new FakeEmbeddingsProvider();
```

**FakeVectorStore**

Returns predetermined documents from `similaritySearch()` regardless of the embedding passed in. Pass the documents you want returned to the constructor:

```php
use NeuronAI\RAG\Document;
use NeuronAI\Testing\FakeVectorStore;

$vectorStore = new FakeVectorStore([
    new Document('Paris is the capital of France.'),
    new Document('Berlin is the capital of Germany.'),
]);
```

**RAG Chat**

```php
public function test_rag_answers_from_documents(): void
{
    $provider = new FakeAIProvider(
        new AssistantMessage('Paris is the capital of France.')
    );

    $vectorStore = new FakeVectorStore([
        new Document('France is a country in Europe. Its capital is Paris.'),
    ]);

    $rag = MyRAG::make()
        ->setAiProvider($provider);
        ->setEmbeddingsProvider(new FakeEmbeddingsProvider());
        ->setVectorStore($vectorStore);

    $message = $rag->chat(new UserMessage('What is the capital of France?'))->getMessage();

    $this->assertSame('Paris is the capital of France.', $message->getContent());
    $provider->assertCallCount(1);
    $vectorStore->assertSearchCount(1);
}
```

**Adding Documents**

Test that your indexing pipeline correctly embeds and stores documents:

```php
public function test_documents_are_embedded_and_stored(): void
{
    $embeddings = new FakeEmbeddingsProvider();
    $vectorStore = new FakeVectorStore();

    $rag = MyRAG::make()
        ->setAiProvider(new FakeAIProvider());
        ->setEmbeddingsProvider($embeddings);
        ->setVectorStore($vectorStore);

    $rag->addDocuments([
        new Document('First document'),
        new Document('Second document'),
    ]);

    $embeddings->assertCallCount(2);
    $vectorStore->assertDocumentCount(2);
    $vectorStore->assertHasDocumentWithContent('First document');
}
```

**RAG Assertions**

```php
// FakeEmbeddingsProvider
$embeddings->assertCallCount(2);
$embeddings->assertEmbeddedText('Some specific text');
$embeddings->assertNothingEmbedded();

// FakeVectorStore
$vectorStore->assertSearchCount(1);
$vectorStore->assertDocumentCount(3);
$vectorStore->assertHasDocumentWithContent('Expected content');
$vectorStore->assertNothingStored();
```


# Introduction

Learn what Neuron is and what you can do with it.

### What is Neuron

Neuron is a PHP framework for developing agentic applications. By handling the heavy lifting of orchestration, data loading, and debugging, Neuron clears the path for you to focus on the creative soul of your project. From the first line of code to a fully orchestrated multi-agent system, you have the freedom to build AI entities that think and act exactly how you envision them.

We provide tools for the entire agentic application development lifecycle, from LLM interfaces, to data loading, to multi-agent orchestration, to monitoring and debugging. In addition, we provide [tutorials and other educational content](/overview/fast-learning-by-video) to help you get started using AI Agents in your projects.

<figure><img src="/files/X4g0nU5EGJ0bn1dbtQcy" alt=""><figcaption><p>Neuron architecture</p></figcaption></figure>

### Getting Started In 3 Steps

**1) Install** Neuron in you project:

```shellscript
composer require neuron-core/neuron-ai
```

**2) Create** an agent extending the `Agent` class:

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
}
```

**3) Talk** with the agent:

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

$message = MyAgent::make()
    ->chat(new UserMessage("Hi, who are you?"))
    ->getMessage();

echo $message->getContent();
// I'm a friendly AI Agent built with Neuron AI framework, how can I help you today?
```

### Demo with Laravel

Neuron offers a well defined encapsulation pattern, allowing you to work on your AI components in a dedicated namespace. You can enjoy the exact same experience of the other ecosystem packages you already love, like Filament, Nova, Horizon, etc.

<a href="https://www.youtube.com/watch?v=oSA1bP_j41w" class="button primary" data-icon="youtube">Watch the demo</a>

<a href="https://github.com/neuron-core/neuron-ai" class="button primary" data-icon="laravel">Official Laravel Package</a>

### Demo with Symfony

All Neuron components belong to its own interface, so you can easily define dependencies and automate objects creation using the Symfony service container. Watch how it works in a real project.

<a href="https://www.youtube.com/watch?v=JWRlcaGnsXw" class="button primary" data-icon="youtube">Symfony & Neuron</a>

### Support For Multiple Providers

Neuron uses a common interface for large language models (`AIProviderInterface`) as well as for the other components, such as [embedding](/rag/embeddings-provider), [vector stores](/rag/vector-store), [toolkits](/agent/tools#toolkits-composable-agent-capabilities), etc. The modular architecture allows you to swap components as needed, whether you're changing LLM provider, adjusting memory backends, or scaling across multiple servers.

Here are a couple of examples:

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

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Ollama" %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Ollama\Ollama;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="OpenAI" %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\OpenAI;

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

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Gemini" %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Gemini;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Gemini(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_MODEL',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Mistral" %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Mistral;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Mistral(
            key: 'MISTRAL_API_KEY',
            model: 'MISTRAL_MODEL',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}
{% endtabs %}

Check out all the supported providers in the [AI Provider](/providers/ai-provider) section.

### Video Tutorials

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

More resources here: [Video Tutorials](/overview/fast-learning-by-video#video)

### Why Neuron

Your next application will be agentic. A growing share of new software is no longer a web application with AI features added along the way, but an application born agentic, where the agent is the architecture itself, driving how the system reasons, acts, and talks to the user interface. Building this kind of application requires a specific set of foundations: event-driven workflows with checkpointing, human-in-the-loop, interruption, multi-agent orchestration, streaming, and agentic UI protocols like AG-UI and the Vercel AI SDK protocol, MCP, and asynchronous execution.

In the PHP ecosystem, this set of foundations exists in one place. Each one is a chapter of this documentation: [Workflow](/workflow/getting-started), [Human in the loop](/agent/middleware#tool-approval-human-in-the-loop), [Streaming & UI protocols](/agent/streaming#stream-adapters), [MCP](/agent/mcp-connector), [Async](/agent/async). You can compare it with any other option available to a PHP developer, and the comparison is the answer.

There is also no second framework waiting for you when the project grows. The same Workflow that runs your first agent in the getting started guide runs a multi-agent system with state, loops, and human approvals in production. What you learn on day one is what you ship in future projects.

### A Vertical & Independent Ecosystem

Neuron is also the only vertical ecosystem for agentic applications development in PHP. Around the framework there is a registry of extensions, tools, and technologies designed specifically for agentic-native applications, and a growing number of companies building on the same architecture instead of assembling their own from scattered parts.

For a software house, this is a place to be recognized as a specialist rather than one more team claiming AI experience. For a company that needs an agentic foundation it can commit to for years, it means standardizing on an architecture whose whole direction is this space, not a general-purpose library where agents are a side feature.

## Resources

### [E-Book - "Start With AI Agents In PHP"](https://www.amazon.it/dp/B0F1YX8KJB)

The gap between modern agentic technologies and traditional PHP development has been widening in recent years. While Python developers enjoy a wealth of libraries and frameworks to create AI Agents, PHP developers have often been left wondering how they can participate in this technological revolution without completely retooling their skillsets or rebuilding their applications from scratch.

Neuron changes all that.

This book serves as both an introduction to AI Agents concepts for developers and a comprehensive guide to Neuron framework.

<a href="https://www.amazon.com/dp/B0F1YX8KJB" class="button secondary" data-icon="amazon">Get on Amazon</a>

<a href="https://play.google.com/store/books/details?pcampaignid=books_read_action&#x26;id=agJPEQAAQBAJ&#x26;pli=1" class="button secondary" data-icon="google">Get on GooglePlay</a>

### [Newsletter](https://neuron-ai.dev)

Register to the Neuron internal [newsletter](https://neuron-ai.dev/) to get informative papers, articles, and best practices on how to start with AI development in PHP.

You will learn how to approach AI systems in the right way, understand the most important technical concepts behind LLMs, and how to start implementing your AI solutions into your PHP application with the Neuron AI framework.

### [Forum](https://github.com/inspector-apm/neuron-ai/discussions)

We’re using [Discussions](https://github.com/inspector-apm/neuron-ai/discussions) as a place to connect with PHP developers working on Neuron to create their Agentic applications. We hope that you:

* Ask questions you’re wondering about.
* Share ideas.
* Engage with other community members.
* Welcome others and are open-minded.

### [**Inspector.dev**](https://inspector.dev)

Neuron is part of the Inspector ecosystem as a trustable platform to create reliable and scalable AI driven solutions.

Trace and evaluate your agents execution flow to help you maintain production grade implementations with confidence. Check out the [**monitoring integrations**](/agent/observability).

## Keep In Touch

* Website & Newsletter: [https://neuron-ai.dev](https://neuron-ai.dev/)
* Repository: [https://github.com/neuron-core/neuron-ai](https://github.com/inspector-apm/neuron-ai)
* Inspector: <https://inspector.dev>
* E-Book: <https://www.amazon.it/dp/B0F1YX8KJB>
* Linkedin: <https://www.linkedin.com/company/neuron-ai-php-framework>
* X: <https://x.com/neuronai_php>
* Instagram: <https://www.instagram.com/neuronai_php_adk/>


# Installation

Step by step instructions on how to install Neuron in your application and create an Agent.

### Requirements

* PHP: ^8.1

### Install

Run the composer command below to install the latest version:

```bash
composer require neuron-core/neuron-ai
```

### Create an Agent

You can easily create your first agent with command below:

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

```bash
./vendor/bin/neuron make:agent App\\Neuron\\MyAgent
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:agent App\Neuron\MyAgent
```

{% endtab %}
{% endtabs %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Agent\SystemPrompt;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }

    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are a friendly AI Agent created with Neuron framework."],
        );
    }
}
```

### Talk to the Agent

Send a prompt to the agent to get a response from the underlying LLM:

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

$message = MyAgent::make()
    ->chat(new UserMessage("Hi, who are you?"))
    ->getMessage();

echo $message->getContent();
// I'm a friendly AI Agent built with Neuron, how can I help you today?
```

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls, tools, external memory system, etc. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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

### Video Tutorial On A Laravel Application

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


# Upgrade

## Upgrade to v3 from v2

In this new major version the public APIs of Neuron components weren't changed dramatically (we minimized the impact as much as possible), but the underlying architecture of Agent, RAG, and the Message system have been completely rebuilt on top of the Workflow component that now powers the entire framework.

Now Agent and RAG are no longer simple objects but workflows. They inherit features that were impossible to integrate in the previous standalone implementation, such as:

* The unified [messaging system](/agent/messages#the-unified-messaging-layer) for multi-modal agents
* Native support for [tool approval](/agent/middleware#human-in-the-loop) and fully customizable human-in-the-loop flows
* Multi-agent [streaming](https://docs.neuron-ai.dev/workflow/streaming) and collaboration.

We also took advantage of this release to fix other critical design issues emerged in the v2 like the **complete support for reasoning models across all providers**, and other design improvements to have more freedom to evolve the framework with less breaking changes in the future.

We continue to work to provide the best possible developer experience, to help you create successful AI products in PHP.

## Updating Dependencies

You should update the following dependencies in your application's `composer.json` file:

* **neuron-core/neuron-ai** to **^3.0**

## High Impact Changes

### New Agent namespace

The Agent class and related classes and traits have been moved from the root directory under the dedicated namespace `NeuronAI\Agent`.

You need to update the namespace in the files where you use the Agent class, from:

```php
use NeuronAI\Agent;
```

To:

```php
use NeuronAI\Agent\Agent;
```

The same for the SystemPrompt class. The new namespace is `NeuronAI\Agent\SystemPrompt`.

### Remove chatAsync()

The `chatAsync()` method was completely removed from the `AgentInterface`. If you are using this method in your application you have to switch to the new async pattern.

<a href="/pages/GTly1F5NQOvWuGWddsmc" class="button primary" data-icon="arrow-right-long">Learn about Async</a>

### Agent return type

Since the Agent is now a workflow, you have slightly different APIs to actually run the agent and retrieve the LLM response.

Previously you will get an instance of a Message directly from the `chat()` method. Now the chat method returns a workflow state that you can use to retrieve the final agent response.

The returning agent state allows you to eaily access the LLM response, but it makes also possible the inspection of other aspects of the internal execution of the agent. Here is an example of the new syntax to run an agent, and output the content generated by the LLM.

```php
// Previous versions chat() return the LLM response
$message = MyAgent::make()->chat(new UserMessage("Hi, who are you?"));

// V3 - you need to call "getMessage()"
$message = MyAgent::make()
    ->chat(new UserMessage("Hi, who are you?"))
    ->getMessage();

echo $message->getContent();
```

### Message Content Blocks

The content blocks now replace the old approach based on "attachments". The legacy attachment system has been removed. To migrate:

**Old Approach** (no longer available):

```php
$message = new UserMessage('Analyze this');
$message->addAttachment(new Image($url, AttachmentContentType::URL));
```

**New Approach**:

```php
// Simple text message (backward compatible)
$message = new UserMessage("Hi");

// New format for passing images, files, etc.
$message = new UserMessage([
    new TextBlock('Analyze this'),
    new ImageBlock($url, SourceType::URL)
]);

// Adding more blocks
$message->addContent(
    new TextBlock('Remeber to answer as you are a professinal concierge.')
);

// Print all the text content blocks
echo $message->getContent();
```

The method `getContent()` didn't change, but now returns all text blocks concatenated skipping media types.

Block composition unlock multimodality support, and can be very helpful if you need to inject additional prompts or instructions dynamically along the execution.

<a href="/pages/P9eTDdlLvv6fmU0ydbyi" class="button primary" data-icon="arrow-right-long">Learn about Messages</a>

### Streaming Chunks

In previous versions the streaming interface will return simple string for LLM response chunk, and `ToolCallMessage`, or `ToolCallResultMessage` instances directly for tool related operations. This creates too direct a coupling between the message instances and your application reading the stream.

We implemented dedicated chunk classes `TextChunk`, `ReasoningChunk`, `ToolCallChunk`, `ToolResultChunk`, and others, in order to have dedicated containers for each kind of stream delta. This more clear separation of concerns opened the door to the implementation of the [adapter system](#streaming-adapters), and give us more freedom to improve this layer in the future with less breaking changes to the unified message system.

#### ToolCallChunk

In the previous version Neuron stream directly the `ToolCallMessage` instance with the list of tools involved in the iteration. Now you get a dedicated `ToolCallChunk` for each tool the model is asking to run.

<a href="/pages/XxwS7JuK4IEbdVVsJLNy" class="button primary" data-icon="arrow-right-long">Read more about streaming</a>

### Structured Output

We extended the role of the `SchemaProperty` attribute to be the source of truth for the JSON schema definition of a class property. It now supports `min`, `max`, `minLength`, `maxLength`, `anyOf`.

```php
use NeuronAI\StructuredOutput\SchemaProperty;

class Person 
{
    #[SchemaProperty(
        description: 'The user name.',
        required: true,
        minLength: 3,
        maxLength: 255,
    )]
    public string $name;
    
    #[SchemaProperty(
        description: 'What the user love to eat.', 
        required: false,
        min: 18,
        max: 64,
    )]
    public ?int $age = null;
}
```

#### Array of objects

If a property is an array of structured object, you no longer need to specify the doc-block of the property types, you can just list them in the `anyOf` argument:

```php
class Report
{
    #[SchemaProperty(
        description: 'The content of the report', 
        required: true,
        anyOf: [TextBlock::class, TableBlock::class, ImageBlock::class]
    )]
    public array $content;
}
```

<a href="/pages/mSJxEmhFwYQYzLH0TkvV" class="button primary" data-icon="arrow-right-long">Structured Output</a>

### Workflow Interrupt Request (Human In The Loop)

In the previous version when you asked for an interrupt inside a Node you could pass an array of data to inform the client about the reason and the actions behind the interruption.

{% code title="Old syntax" %}

```php
$feedback = $this->interrupt(['message' => 'do you want to approve?']);
```

{% endcode %}

This lazy typed method led to inconsistencies and errors. We introduced the `InterruptRequest` primitive to help you create interrutpion flows with a typed structure for a safe UI integration.

{% code title="New syntax" %}

```php
$feedback = $this->interrupt(new ApprovalRequest(
    reason: 'Do you want to approve?',
    actions: [
        new Action(...)
    ]
));
```

{% endcode %}

Learn more in the dedicated section of the documentation.

<a href="/pages/6SYuJtkladCJRbTO7hEL" class="button primary" data-icon="arrow-right-long">Workflow interruption</a>

### Workflow Database Persistence Change

The name of the columns for the workflow persistence database table changed:

* data -> interrupt

<a href="/pages/rM5zmOIVvcgRCLyvIY1S" class="button primary" data-icon="arrow-right-long">Workflow presistence</a>

## Medium Impact

### Rename ToolCallResultMessage

This class was renamed to `ToolResultMessage`.

### Monitoring & Observers

Agent, RAG, and Workflow entities no longer implement the PHP `\SplSubject` interface, and the observer classes no longer implement the `\SplObserver` interface. We introduced the new `ObserverInterface` that must be implemented only by event listeners like `LogObserver`. This lighter structure helped us to make the workflow building blocks observable like Workflow, node, and middleware. This means you can emit events from your custom nodes, and you only need to create and register your custom observer to listen for these events.

Read more on the [Monitoring section](/agent/observability).

### Remove HttpClientOptions

This class was removed in favor a complete abstraction of the HttpClient inside the framework. We adopted an adapter pattern to allow you inject custom http clients into the framework components, and customize their configuration. The Guzzle client adapter also support handler stack, custom headers, etc.

You can see an example of how to customize the Http client configuration in the [Async](/agent/async) section.

### Qdrant 1.10.x

The Qdrant vector store components was updated to support the new [query APIs](https://api.qdrant.tech/api-reference/search/query-points) that are included starting from the version 1.10.x. If you use a previous version of the Qdrant database you need to upgrade your instance.

### AbstractChatHistory methods signature

If you have implemented a custom chat history component you need to adjust the signature of the hook methods. They changed the visibility level, from public to protected, and they no longer have a return type:

```php
class MyChatHistory extends AbstractChatHistory
{
    protected function setMessages(array $messages): void
    {
        // Handle saving the entire history at once.
    }

    protected function onNewMessage(Message $message): void
    {
        // Handle single message addition.
    }

    protected function onTrimHistory(int $index): void
    {
        // When the trim is triggered, the messages in the position from zero to $index must be removed.
    }

    protected function clear(): void
    {
        // Remove all messages.
    }
}
```

## New Features

### Tool Approval & Conditional Approval

Thanks to the human in the loop pattern supported by the underlying workflow architecture, we created a built-in middleware to enable Tool approval in your agent like a plu\&play feature:

```php
new ToolApproval(
    tools: [
        BuyTicketTool::class => function (array $args): bool {
            return $args['amount'] > 100;
        }
    ]
)
```

<a href="/pages/bFnrksGXQFcgecFqBMBF#tool-approval-human-in-the-loop" class="button primary" data-icon="arrow-right-long">Tool Approval</a>

### Mistral Dedicated Provider

Mistral provider is no longer a pure OpenAI implementation, but it was evolved with its own API format implementation to support multi-modal input and reasoning models.

<a href="/pages/Hh6oxTynZkuci7DvDDm7#mistral" class="button primary" data-icon="arrow-right-long">Mistral AI Provider</a>

### Cohere AI Provider

This version ships with a brand new provider to support Cohere inference platform both cloud and privately deployed.

<a href="/pages/Hh6oxTynZkuci7DvDDm7#cohere" class="button primary" data-icon="arrow-right-long">Cohere AI Provider</a>

### Text-To-Speech providers

Thanks to the new block composition of messages it's easy now to deal with input and output multimodality. In this release we included a couple of providers you can use to process audio contents.

<a href="/pages/lSLXWfaXukHXLPR0UIaq" class="button primary" data-icon="arrow-right-long">Text-To-Speech providers</a>

### Streaming Adapters

Adapters act as translators between Neuron's internal streaming events (text chunks, tool calls, reasoning steps) and specific frontend protocols like Vercel AI SDK, AG-UI, or your custom frontend needs.

This architecture allows you to seamlessly integrate Neuron agents with various frontend frameworks (React, Vue, etc.) without modifying your core agent logic.

<figure><img src="/files/L02GGuYBoNrkOZqUAtNU" alt=""><figcaption></figcaption></figure>

<a href="/pages/XxwS7JuK4IEbdVVsJLNy#stream-adapters" class="button primary" data-icon="arrow-right-long">Learn more about Adapters</a>

### File ID content block

Usually you can attach files to your message (images or documents) as URLs, or encoded in base64 format. Many provider allows you to upload files on their platform once, and reference these files with a simple ID in the message. This can generate big savings in token consumption and can improve model response time.

After receiveing the file ID from the provider platofrm you can add a file block to your message with `SourceType::ID`.

```php
// Reference a file ID previously uploaded on the provider platform
$message = new UserMessage([
    new TextBlock('Analyze this'),
    new FileBlock("file_id_xxxx", SourceType::ID)
]);
```

You can do the same with Image, Video, etc, based on your provider specifications.

### Middleware

Middleware provides a way to tightly control what happens inside the workflow and therefore also in your Agents and RAGs, since they too are workflows now.

The core Workflow execution involves calling nodes based on the events returned by other nodes. Middleware exposes hooks to step inside `before` and `after` the execution of nodes:

<figure><img src="/files/WLjXxevp8EFF94o0wv4N" alt=""><figcaption></figcaption></figure>

This architecture has been used to create the [buit-in middlewares](/agent/middleware) for the Agent class, like context summarization, or tool approval.

<a href="/pages/Ubrq4aUuPXAWrv0BAuFR" class="button primary" data-icon="arrow-right-long">Learn more about Middleware</a>


# Video Tutorials

Position yourself in the AI Agent era with our extensive tutorials and technical insights into Neuron capabilities. Learn from practical examples and real-world use cases.

## Video

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

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

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

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

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

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

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

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

## Articles

### Agent Development

[PHP, the Dark Horse No One Saw Coming In AI Agents development](https://inspector.dev/php-the-dark-horse-no-one-saw-coming-in-ai-agents-development/)

[LangChain alternative for PHP developers](https://inspector.dev/langchain-alternative-for-php-developers/)

[System Prompt for AI Agents In PHP](https://inspector.dev/system-prompt-for-ai-agents-in-php/)

[AI Agents Memory And Context Window In PHP](https://inspector.dev/ai-agents-memory-and-context-window-in-php/)

[Create AI Agents In PHP Powered By Google Gemini LLMs](https://inspector.dev/create-ai-agents-in-php-powered-by-google-gemini-llms/)

### RAG (Retrieval Augmented Generation)

[How to Create a RAG Agent with Neuron ADK for PHP](https://inspector.dev/how-to-create-a-rag-agent-with-neuron-adk-for-php/)

[Vector Store & AI Agents – Beyond The Traditional Data Storage](https://inspector.dev/vector-store-ai-agents-beyond-the-traditional-data-storage/)

[Improve PHP AI Agents output quality with Rerankers](https://inspector.dev/improve-php-ai-agents-output-quality-with-rerankers/)

### Tools & Toolkits

[Introducing Toolkits: Composable AI Agent Capabilities In PHP](https://inspector.dev/introducing-toolkits-composable-ai-agent-capabilities-in-php/)

[Create A Data Analyst Agent In PHP – Neuron MySQL Toolkit](https://inspector.dev/mysql-ai-toolkit-bringing-intelligence-to-your-database-layer-in-php/)

[Introducing Web Search Capabilities For PHP AI Agents](https://inspector.dev/introducing-web-search-capabilities-for-php-ai-agents/)

[Introducing Vision Capabilities for PHP AI Agents](https://inspector.dev/introducing-vision-capabilities-for-php-ai-agents/)

[AI Agents in PHP with MCP (Model Context Protocol)](https://inspector.dev/ai-agents-in-php-with-mcp-model-context-protocol/)

### Workflow

[Introducing Neuron Workflow: The future of agentic PHP applications](https://inspector.dev/introducing-neuronai-workflow-the-future-of-agentic-php-applications/)

[Deep Research Agent Implementation](https://inspector.dev/multi-agent-systems-in-php-a-practical-deep-research-implementation/)

[Laravel Travel Agent](https://inspector.dev/building-multi-agent-systems-in-laravel-a-practical-demo/)

[Managing Human-in-the-Loop With Checkpoints](https://inspector.dev/managing-human-in-the-loop-with-checkpoints-neuron-workflow/)

## E-Book

The gap between modern agentic technologies and traditional PHP development has been widening in recent years. While Python developers enjoy a wealth of libraries and frameworks to create AI Agents, PHP developers have often been left wondering how they can participate in this technological revolution without completely retooling their skillsets or rebuilding their applications from scratch.

Neuron changes all that.

<figure><img src="/files/icOUu6fFXtKRtH0mEBG9" alt="" width="375"><figcaption></figcaption></figure>

As a PHP developer, you now stand at a unique intersection of technologies. For years, PHP has powered a substantial portion of the web. Now, with Neuron AI, you have the ability to infuse these web experiences with artificial intelligence, without leaving the language and ecosystem you know and love.

Neuron is the most advanced PHP framework to build AI driven applications. This book serves as both an introduction to AI Agents concepts for developers and a comprehensive guide to Neuron PHP agentic framework.

Get it from [Amazon](https://www.amazon.com/dp/B0F1YX8KJB) or [Google Play](https://play.google.com/store/books/details?pcampaignid=books_read_action\&id=agJPEQAAQBAJ\&pli=1).

<a href="https://www.amazon.com/dp/B0F1YX8KJB" class="button secondary" data-icon="amazon">Amazon Books</a>

<a href="https://play.google.com/store/books/details?pcampaignid=books_read_action&#x26;id=agJPEQAAQBAJ&#x26;pli=1" class="button secondary" data-icon="google">Google Play</a>


# AI-Assisted Development

Connect the documentation to coding agents for AI Assisted Development

When working with AI coding assistants like Claude Code, Opencode, Cursor, or other similar tools, you can reference the Neuron AI documentation to give the AI deep context about our components. This leads to more accurate code suggestions, better understanding of component APIs, and fewer hallucinations when generating Neuron code.

## Agent Skills

The [Agent Skills specification](https://agentskills.io/) is a standard for providing structured documentation to AI coding assistants. It helps AI tools understand your project's APIs, conventions, and best practices through a well-organized directory of markdown files.

Neuron publishes an Agent Skill that provides AI tools with comprehensive information about our components, including their APIs, usage patterns, interfaces, and more.

### Accessing Skills

{% hint style="info" %}
Type **`/neuron-*`** in your terminal.
{% endhint %}

The Agent Skill is available in the Neuron AI vendor folder at:

```bash
vendor/neuron-core/neuron-ai/skills/
        └── neuron-agent-builder/
            └── SKILL.md
        └── neuron-debugger/
            └── SKILL.md
        └── neuron-evaluation-engineer/
            └── SKILL.md
        └── neuron-rag-specialist/
            └── SKILL.md
        └── neuron-structured-output/
            └── SKILL.md
        └── neuron-tool-creator/
            └── SKILL.md
        └── neuron-test-engineer/
            └── SKILL.md
        └── neuron-tool-creator/
            └── SKILL.md
        └── neuron-workflow-architect/
            └── SKILL.md

```

### How to install skills

How you reference the skill depends on which AI tool you're using.

#### **Claude**

If you're using [Claude Code](https://claude.ai/code), you can install the Neuron AI skills locally using the [skills CLI](https://skills.sh/):

```bash
npx skills add ./vendor/neuron-core/neuron-ai/skills
```

Once installed, the skill will be available to Claude Code automatically. The skilla are installed as a symlink, so it will automatically stay up to date when you update Neuron via composer.

#### Cursor <a href="#cursor" id="cursor"></a>

In [Cursor](https://cursor.sh/), you can add the skill directory to your project's documentation sources via **Cursor Settings > Features > Docs**. Point it to the `vendor/neuron-core/neuron-ai/skills` .

#### Other AI Tools <a href="#other-ai-tools" id="other-ai-tools"></a>

Most AI coding assistants that support the Agent Skills specification can use this skill. Check your tool's documentation for how to add custom skills or documentation sources.

## MCP Server

This documentation is also available and searchable as a Model Context Protocol (MCP) server. This allows AI assistants to access Neuron AI documentation content directly. The MCP server is available at: <https://docs.neuron-ai.dev/~gitbook/mcp>

### Claude Code

```
claude mcp add --transport http neuron-ai-doc https://docs.neuron-ai.dev/~gitbook/mcp
```

### VS Code

```json
"mcp": {
    "servers": {
        "neuron-ai-doc": {
            "type": "http",
            "url": "https://docs.neuron-ai.dev/~gitbook/mcp"
        }
    }
}
```

### Cursor

```json
{
  "mcpServers": {
    "neuron-ai-doc": {
        "url": "https://docs.neuron-ai.dev/~gitbook/mcp"
    }
  }
}
```

### Windsurf

```json
{
  "mcpServers": {
    "neuron-ai-doc": {
      "serverUrl": "https://docs.neuron-ai.dev/~gitbook/mcp"
    }
  }
}
```

### OpenCode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "neuron-ai-doc": {
      "type": "remote",
      "url": "https://docs.neuron-ai.dev/~gitbook/mcp",
      "enabled": true
    }
  }
}
```


# Agent

Easily implement LLM interactions with built-in memory and tool usage.

### Introduction

You can create your agent by extending the `NeuronAI\Agent\Agent` class to inherit the main features of the framework and create fully functional agents.

This class automatically manages some mechanisms for you such as memory, tools and function calls. We will go into more detail about these aspects in the following sections.

We strongly encourage to extend the Agent class instead of creating agents using the [fluent definition](#fluent-agent-definition). This strategy make it easier to add custom methods and behaviour to the agent, and also promote portability, because all the moving parts are encapsulated into a single entity that you can run wherever you want in your application, or even release as a stand alone composer package.

Let's start creating an AI Agent summarizing YouTube videos. We start creating the `YouTubeAgent` class:

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

```bash
vendor/bin/neuron make:agent App\\Neuron\\YouTubeAgent
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:agent App\Neuron\YouTubeAgent
```

{% endtab %}
{% endtabs %}

The command will create a class like this:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Agent\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an instance of Anthropic, OpenAI, Gemini, Ollama, etc...
    }
    
    protected function instructions(): string
    {
        return "You are a friendly AI Agent created with Neuron AI framework.";
    }
    
    /**
     * @return \NeuronAI\Tools\ToolInterface[]
     */
    protected function tools(): array
    {
        return [];
    }
}
```

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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

### AI Provider

The minimum implementation requires assigning an AI Provider that will be the language and reasoning engine of your agent.

The only required method to implement is `provider()` returning the instance of the provider you want to use. Let's assume it's Anthropic.

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Agent\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an instance of Anthropic, OpenAI, Gemini, Ollama, etc...
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    protected function instructions(): string
    {
        return "You are a friendly AI Agent created with Neuron AI framework.";
    }
    
    /**
     * @return \NeuronAI\Tools\ToolInterface[]
     */
    protected function tools(): array
    {
        return [];
    }
}
```

You can also use other providers like OpenAI, Gemini, or Ollama if you want to run the model locally. Check out the [supported providers](/providers/ai-provider).

### System instructions

The second important building block is the system instructions. System instructions provide directions for making the AI ​​act according to the task we want to achieve. They are fixed instructions that will be sent to the LLM on every interaction.

That’s why they are defined by an internal method, and stay encapsulated into the agent entity. Let's implement the `instructions()` method:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Agent\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider instance (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    protected function instructions(): string
    {
        return <<<TEXT
            You are an AI Agent specialized in writing YouTube video summaries.
            Get the url of a YouTube video, or ask the user to provide one.
            Use the tools you have available to retrieve the transcription of the video.
            Write a summary in a paragraph without using lists. Use just fluent text.
            After the summary add a list of three sentences as the three most important take away from the video.
        TEXT;
    }
    
    /**
     * @return \NeuronAI\Tools\ToolInterface[]
     */
    protected function tools(): array
    {
        return [];
    }
}
```

### Talk to the Agent

We are ready to test how the agent responds to our message based on the new instructions.

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

$message = YouTubeAgent::make()
    ->chat(new UserMessage("Who are you?"))
    ->getMessage();
    
echo $message->getContent();
// Hi, I'm a frindly AI agent specialized in summarizing YouTube videos!
// Can you give me the URL of a YouTube video you want a quick summary of?
```

### Agent State

Since the Agent is an extension of the Workflow, instead of getting the last model response with the `getMessage()` method, you cvan just run the agent workflow, and get the raw agent state as return value. The agent state contains additional information that can help you inspect what happened during the agent execution.

```php
$state = MyAgent::make()
    ->chat(new UserMessage("Who are you?"))
    ->run();

// $state is an instance of NeuropnAI\Agent\AgentState class
$state->getMessage();
```

#### Steps

Calling the `getMessage()` method you are only able to get the last message generated by the model to answer your prompt. But internally the agent can performs many tool call iterations before coming up with the final answer.

The agent state stores the list of all messages between the agent and the provider for the current execution cycle, rather than only the final answer. So you can access the list of messages with the `getSteps()` method on the agent state:

```php
$state = MyAgent::make()
    ->chat(new UserMessage("Who are you?"))
    ->run();

// Access the list of steps during the execution
foreach($state->getSteps() as $message) {
    echo "- ".$message::class."\n";
}

// The final answer
echo $state->getMessage()->getContent();
```

#### Tool Runs

If the agent decide to use tools during the execution, the agent state keeps track iof thethe number of tool runs to stop the execution if the [maxRuns](/agent/tools#max-runs) limit is reached. You can access this map:

```php
$state = MyAgent::make()
    ->chat(new UserMessage("Who are you?"))
    ->run();

// Access the tool runs map
foreach($state->getToolRuns() as $toolName => $runs) {
    echo "- The tool {$toolName} was used {$runs} times\n";
}
```

### Message

The agent always accepts input as a `Message` class, and returns Message instances.

As you saw in the example above we sent a `UserMessage` instance to the agent and we retrieve the reply message that will be an `AssistantMessage` instance. A list of assistant messages and user messages creates a chat.

We will learn more about [ChatHistory](/agent/chat-history-and-memory) later, but it's important to know that the unified interface for the agent input and output is the `Message` object.

<a href="/pages/P9eTDdlLvv6fmU0ydbyi" class="button primary" data-icon="arrow-right-long">Learn more about Messages</a>

### Fluent Agent Definition

In alternative to the single class encapsulation you can also instruct the agent inline using the fluent chain of methods:

```php
$agent = Agent::make()
    ->setAiProvider(
        new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        )
    )
    ->setInstructions(
        "New system instructions..."
    )
    ->addTool([...]);
    
$message = $agent->chat(new UserMessage(...))->getMessage();
echo $message->gentContent();
```


# Messages

Unified context unit across AI providers and LLMs.

## The Unified Messaging Layer

One of the major advantages of Neuron's architecture is the unified messaging layer to interact with multiple AI providers. Rather than wrestling with different response formats of OpenAI, Anthropic, Gemini, Ollama, and countless other providers, developers work with a single, elegant abstraction that handles all the complexity behind the scenes. Your code becomes independent of the specific LLM engine you use.

When you integrate Neuron Agents in your application, you're not locked into any specific provider's ecosystem or pricing model. You can seamlessly switch between providers to manage costs, environments, or take advantage of new model releases to capitalize on these improvements immediately without undertaking a major refactoring project.

**The messaging layer extends beyond simple text content, providing unified interfaces for multimodal input/output** (file, image, video, audio) across all supported providers, even when underlying implementations vary dramatically. This architectural decision means that your AI agents remain portable and future-proof – when new providers emerge or existing ones update their APIs, your application code remains unchanged while Neuron's messaging layer absorbs all the adaptation complexity.

## What is a Message

Messages are the fundamental unit of context. They represent the input and output of models, carrying both the content and metadata needed to represent the state of a conversation when interacting with an LLM.

Messages are objects that contain:

* **Role** - Identifies the message type (e.g. user, assistant)
* **Content Blocks** - Represents the actual content of the message (like text, images, audio, files, etc.)
* **Metadata** - Optional fields such as additional LLM response information.

Here is an example of how to send a user message to the agent and get back the assistant message as response.

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

$response = MyAgent::make()
    ->chat(new UserMessage("Hi, who are you?"))
    ->getMessage();

echo $response->getContent();
```

## Content Blocks

You can think of a message’s content block as the payload of data that gets sent to the model, or generated by the model to answer your prompt. Messages can have a list of objects extending the `ContentBlock` interface. Neuron provides dedicated content types for text, image, file, audio, and video.

The message can contains an arbitrary list of blocks, even multiple blocks of each type. Neuron automatically maps block types in the appropriate format for each provider.

You can get and process the list of blocks into a message using `getContentBlocks()` method:

```php
$response = MyAgent::make()->chat(...)->getMessage();

foreach ($response->getContentBlocks() as $block) {
    echo match($block::class) {
        ReasoningContent::class => "Reasoning: ".$block->content."\n\n",
        TextContent::class => $block->content,
        ...
        // other content blocks 
    };
}
```

Or just use `getContent()` to get all the textual contents concatenated:

```php
$response = MyAgent::make()
    ->chat(new UserMessage("..."))
    ->getMessage();

// Get all text blocks concatenated as a single string
echo $response->getContent();
```

{% hint style="info" %}

#### Verify Model Capabilities

Before using specific content blocks you need to verify the model capabilities to interpret the information you want to send (image, audio, video).
{% endhint %}

### Text

This block represent the text part of a message. You can initialize a message with a simple string as contructor argument, or explicitly add a `TextContent` block:

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

// Passing a string in the constructor will add the first TextContentBlock to the message
$message = new UserMessage("Hi");

// Add other text parts to the message
$message->addContent(
    new TextContent("My name is John.")
);

$message->addContent(
    new TextContent('Remeber to answer as you are a professinal concierge.')
);

// Get all text blocks concatenated
echo $message->getContent();
// Hi my name is John. Remeber to answer as you are a professinal concierge.

// Or get the array of text content blocks
$blocks = $message->getTextBlocks();
```

As you can notice the final message will be a composition of multiple blocks.

### Reasoning

This block will contain the resoning steps the model made before the final text response. It's automatically captured by Neuron from the model response:

```php
// Chat with a resoning model
$response = MyAgent::make()->chat(...)->getMessage();

foreach ($response->getContentBlocks() as $block) {
    echo match($block::class) {
        ReasoningContent::class => "Reasoning: ".$block->content."\n\n",
        TextContent::class => $block->content,
        ...
        // other content blocks 
    };
}
```

### Image

For models that support multimodality you can attach images and other type of contents, like files, audio, and video.

```php
use NeuronAI\Chat\Enum\MediaType;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Chat\Messages\ContentBlocks\ImageContent;

$message = new UserMessage("Describe this image");

$message->addContent(
    new ImageContent(
        source: 'https://placehold.co/600x400/EEE/31343C',
        sourceType: SourceType::URL,
        mediaType: MediaType::PNG
    )
);

$response = MyAgent::make()->chat($message)->getMessage();
echo $response->getContent();
```

### File

```php
use NeuronAI\Chat\Enum\MediaType;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Chat\Messages\ContentBlocks\FileContent;

$message = new UserMessage("Summarize this document");

$message->addContent(
    new FileContent(
        source: base64_encode(file_get_contents(__DIR__.'/invoice.pdf')),
        sourceType: SourceType::BASE64,
        mediaType: MediaType::PDF
    )
);

$response = MyAgent::make()->chat($message)->getMessage();
echo $response->getContent();
```

### File ID

Usually you can attach files to your message (images or documents) as URLs, or encoded in base64 format. Many provider allows you to upload files on their platform once, and reference these files with a simple ID on the message. This can unlock big saving in token consumption and can improve the model response time.

After receiveing the file ID from the provider platofrm you can add a file block to your message with `SourceType::ID`.

```php
// Reference a file ID previously uploaded on the provider platform
$message = new UserMessage([
    new TextBlock('Analyze this'),
    new FileBlock("file_id_xxxx", SourceType::ID)
]);
```

You can do the same with Image, or Video, etc, based on your provider specifications.

### Audio

```php
use NeuronAI\Chat\Enum\MediaType;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Chat\Messages\ContentBlocks\AudioContent;

$message = new UserMessage("Transcribe this audio");

$message->addContent(
    new FileContent(
        source: base64_encode(file_get_contents(__DIR__.'/music.mp3')),
        sourceType: SourceType::BASE64,
        mediaType: MediaType::MP3
    )
);

$response = MyAgent::make()->chat($message)->getMessage();
echo $response ->getContent();
```

### Video

```php
use NeuronAI\Chat\Enum\MediaType;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Chat\Messages\ContentBlocks\VideoContent;

$message = new UserMessage("Summarize the content of this lesson.");

$message->addContent(
    new VideoContent(
        source: base64_encode(file_get_contents(__DIR__.'/lesson_1.mp4')),
        sourceType: SourceType::BASE64,
        mediaType: MediaType::MP$
    )
);

$response = MyAgent::make()->chat($message)->getMessage();
echo $response->getContent();
```


# Tools & Toolkits

Give Agents the ability to interact with your application context and services.

The core agent loop involves calling a model, letting it choose tools to execute, and then finishing when no more tools are needed to provide a response:

<figure><img src="/files/USrbHNEn2eAonVgDrLZ4" alt=""><figcaption></figcaption></figure>

### What is a Tool

Tools enable Agents to go beyond generating text by facilitating interaction with your application services, or external APIs.

Think about Tools as special functions that your AI agent can use when it needs to perform specific tasks. They let you extend your Agent's capabilities by giving it access to specific functions it can call inside your code.

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

In the [YouTubeAgent](/agent/agent) example we can define a tool to make the Agent able to retrieve the YouTube video transcription, so it can crteate a short summary:

```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
    {
        // return an AI provider instance (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    protected function instructions(): string 
    {
        return (string) new SystemPrompt(
            background: ["You are an AI Agent specialized in writing YouTube video summaries."],
            steps: [
                "Get the url of a YouTube video, or ask the user to provide one.",
                "Use the tools you have available to retrieve the transcription of the video.",
                "Write the summary.",
            ],
            output: [
                "Write a summary in a paragraph without using lists. Use just fluent text.",
                "After the summary add a list of three sentences as the three most important take away from the video.",
            ]
        );
    }
    
    protected function tools(): array
    {
        return [
            Tool::make(
                'get_transcription',
                'Retrieve the transcription of a youtube video.',
            )->addProperty(
                new ToolProperty(
                    name: 'video_url',
                    type: PropertyType::STRING,
                    description: 'The URL of the YouTube video.',
                    required: true
                )
            )->setCallable(function (string $video_url) {
                return "Video transcripton...";
            })
        ];
    }
}

```

Let’s break down the code.

We introduced the new method `tools()` into the Agent class. This method expects to return an array of Tool objects that the AI will be able to use if needed.

In this example we return an array of just one tool, named `get_transcription`.

Notice that the `ToolProperty` we define should match with the signature of the function you use as a callable. The callable gets the `$video_url` arguments, and the name of the property is exactly "video\_url".

The most important thing are the name and description you give to the tool and its properties. All these pieces of information will be passed to the LLM in natural language. The more explicit and clear you are, the more likely the LLM understands when, if, and why, it’s the case to use the tool.

Once the Agent decides to use a tool the callable function is executed. Here we can implement the logic to retrieve the video transcription and return the information back to the LLM.

Neuron provides you with these clear and simple APIs and automates all the underlying interactions with the LLM. Once you get the point it can immediately open to a possibility to connect basically everything you want to the Agent. Being able to execute local functions allows you to invoke any external APIs or application components.

### Custom Tools

Thanks to the Neuron modular architecture, Tools are components that implement `ToolInterface` . You are free to create pre-packaged tool classes to make the agent able to perform sapecific actions, and release them as external composer packages or submit a PR to our repository to have them integrated into the core framework.

To create a new Tool execute the console command below:

{% 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 %}

You can customize the scaffolding of the tool with the code below:

```php
<?php

namespace App\Neuron\Tools;

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

class GetTranscriptionTool extends Tool
{
    protected Client $client;
    
    public function __construct(protected string $key)
    {
        // Define Tool name and description
        parent::__construct(
            'get_transcription',
            'Retrieve the transcription of a youtube video.',
        );
    }
    
    /**
     * Return the list of properties.
     */
    protected function properties(): array
    {
        return [
            new ToolProperty(
                name: 'video_url',
                type: PropertyType::STRING,
                description: 'The URL of the YouTube video.',
                required: true
            )
        ];
    }
    
    /**
     * Implementing the tool logic
     */
    public function __invoke(string $video_url): string
    {
        $response = $this->getClient()
            ->get('transcript?url=' . $video_url.'&text=true')
            ->getBody()
            ->getContents();

        $response = json_decode($response, true);

        return $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,
            ]
        ]);
    }
}
```

**Tool name and description**: Define name and description of the tool in the tool constructor. Invest in prompt engineering to help the model take better decisions.

**The properties method**: Implement this method to return the list of properties the tool expects.

**The `__invoke` method**: Here you need to implement the logic of the tool, and return a result that will be returned back to the model. The PHP `__invoke` magic method is used by default.

Notice how the `__invoke()` method accepts the same arguments defined by the `ToolProperty` . In this example I'm using an external service to retrieve the YouTube video transcription called [Supadata.ai](https://supadata.ai/).

You can attach the tool in the agent class as usual:

```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'),
        ];
    }
}
```

GetTranscriptions is just an example. You can eventually implement other tools to make the Agent able to retrieve other video metadata to enhance its video analysis capabilities.

Finally you can talk to the agent asking for the summary of a YouTube video.

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

$message = YouTubeAgent::make($user)->chat(
    new UserMessage('What about this video: https://www.youtube.com/watch?v=WmVLcj-XKnM')
)->getMessage();
    
echo $message->getContent();

/**

Based on the transcription, I'll provide a summary of this powerful environmental 
message from "Mother Nature":
This video presents ...

Three most important takeaways:

1. Nature has existed ...

2. The wellbeing of humanity is ...

3. How humans choose to act toward Nature determines ...

*/
```

### Max Runs

Agents have a safety mechanism that tracks the number of times a tool is invoked during an execution session. If the agent exceeds this limit, execution is interrupted and the `ToolRunsExceededException` is thrown. By default the limit is 10 calls, and it count for each tool individually.

You can customize this value with the `toolMaxRuns()` method at agent level, or use `setMaxRuns()` on the tool level. **Setting max tries on single tool takes precedence over the global setting**.

```php
try {

    $response = YouTubeAgent::make()
        ->toolMaxRuns(5) // Max number of calls for each tool
        ->addTool(
            // Tool level config takes precedence over the global setting
            CustomTool::make()->setMaxRuns(2)
        )
        ->chat(...)
        ->getMessage();
        
} catch (ToolMaxTriesException $exception) {
    // do something
}
```

### Visibility

You can condition the availability of tools based on custom rules. The Tool class provides you with the `visible` method to determine if the agent should even known this tool exists:

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

If the `visible` method get `false`, the tool will not be available during agent execution.

### Tool Approval

Neuron provides you with full support for the human in the loop pattern including tool approval. It's different from visbility because "approval" is a runtime gatekeeper. The framework intercepts the tool call and pause waiting for the user's final decision.

You can plug this feature into your agent with our built-in [ToolApproval](#tool-properties) middleware.

```php
new ToolApproval(
    tools: [
        BuyTicketTool::class => function (array $args): bool {
            return $args['amount'] > 100;
        }
    ]
)
```

{% content-ref url="/pages/bFnrksGXQFcgecFqBMBF" %}
[Middleware](/agent/middleware)
{% endcontent-ref %}

### Dynamic Tool Search

By default every time the provider is invoked all tools are loaded and transmitted to the backend LLM. A complex production agent connected to email, calendar, drive, CRM, and and multiple MCP servers can easily reach hundreds of tools, each carrying its name, description, parameter schema, and usage hints.

Tool search reframes the tool catalog as something the agent queries on demand rather than something it carries on every request.

You can use the global middleware `ToolSearchMiddleware` to activate dynamic tool selection on your agent:

```php
new ToolSearchMiddleware([
    MyCustomTool::make(),
    ...CalculatorToolkit::make()->tools()
    ...MCPConnector::make([...])->tools()
])
```

{% content-ref url="/pages/bFnrksGXQFcgecFqBMBF" %}
[Middleware](/agent/middleware)
{% endcontent-ref %}

### Monitoring & Debugging

Neuron automatically manages the tool loop for you, based on what the LLM decided to call.

To watch inside this workflow you should connect your Agent to the [Inspector monitoring dashboard](https://inspector.dev/) in order to see the tool call execution flow in real-time.

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

<figure><img src="/files/1G08C77SsiYbNdVXyz2o" alt=""><figcaption></figcaption></figure>

In the image below you can see all the details about the execution of the tool to retrieve the transcription of the video:

<figure><img src="/files/VsBcEyMZwrEe97bQng3i" alt=""><figcaption></figcaption></figure>

## Tool Properties

Neuron allows you to define the format of the data you want to receive into the tool function. You can nest these objects inside each other to define complex data structures.

### ToolProperty

This class represent a simple scalar value like string, int, or boolean.

```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: 'Describe the value you expect',
                required: true,
                nullable: false
            )
        ];
    }
    
    public function __invoke(string $arg){...}
}
```

### ArrayProperty

The `ArrayProperty` allows you to require a list of items with specific characteristics.

Use the argument `items` to specify the data type of the array elements. In the example below we ask for an array of string.

```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: 'Describe the value you expect',
                required: true,
                items: new ToolProperty(
                    name: 'prop',
                    type: PropertyType::STRING,
                    description: 'Describe the value you expect',
                    required: true
                )
            )
        ];
    }
    
    public function __invoke(string $arg){...}
}
```

#### Max and Min limits

The ArrayProperty allows you also to define limitations about the size of the expected array using `minItems` and `maxItems` arguments.

```php
$property = new ArrayProperty(
    name: "tags",
    description: "List of tags associated with the item",
    required: true,
    items: new ToolProperty(
        name: "tag",
        type: PropertyType::STRING,
        description: "A single tag",
        required: true
    ),
    minItems: 1,
    maxItems: 10
);
```

### ObjectProperty

Similar to the array example above you can define an object data structure:

```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 color',
                required: true,
                properties: [
                    new ToolProperty(
                        name: 'r',
                        type: PropertyType::NUMBER,
                        description: 'The red part of the RGB',
                        required: true
                    ),
                    new ToolProperty(
                        name: 'g',
                        type: PropertyType::NUMBER,
                        description: 'The green part of the RGB',
                        required: true
                    ),
                    new ToolProperty(
                        name: 'b',
                        type: PropertyType::NUMBER,
                        description: 'The blue part of the RGB',
                        required: true
                    )
                ]
            )
        ];
    }
    
    public function __invoke(string $arg){...}
}
```

### Structured Tool Input

If the obect you want has many properties you can pass a structured PHP class to the `ObjectProperty` instead of defining the schema manually. Neuron will provide you with an instance of this class as the input argument of the tool function:

```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: 'Combination of colors',
                required: true,
                class: Color::class
            )
        ];
    }
    
    public function __invoke(Color $color){...}
}
```

Here is how the Colors class looks like:

```php
<?php

namespace App\Neuron\Dto;

use NeuronAI\StructuredOutput\SchemaProperty;

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

## Provider Tools

Some providers offer the possibility to use their built-in tools like web\_search, file\_search, and others instead of relying on external services. Even they offer this service they introduce a lot of constraints using these tools. The most flexible and reliable way to add cpabailities to your agents remains the Tools and Toolkit systems.

You can add a provider tool as usual in the tools array of your agent:

```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([...]),
        ];
    }
}
```

Currently only [OpenAIResponses](/providers/ai-provider#openairesponses), [Gemini](/providers/ai-provider#gemini), and [Anthropic](/providers/ai-provider#anthropic) support these tools.

## Toolkits

The philosophy behind Neuron's toolkit system emerged from a fundamental observation during AI Agent Development: while individual tools provide specific capabilities, real-world AI agents often require coordinated sets of related functionalities.

Rather than forcing developers to manually assemble collections of tools for common use cases, Neuron introduces toolkits as an abstraction layer that transforms how we think about agent capability composition. Here is an example of how you can add a toolkit to an 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(),
        ];
    }
}
```

The traditional approach requires instantiating each tool individually. Imagine you want to build agents that need mathematical reasoning – addition, subtraction, multiplication, division, and exponentiation tools must all be declared separately in the agent's tool configuration. This granular approach quickly becomes unwieldy when agents require comprehensive functionality sets.

Toolkits represent Neuron's solution to this complexity, packaging tools created around the same scope into a single, coherent interface that can be attached to any agent with a single line of code.

Here is an example of the `CalculatorToolkit`:

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

use NeuronAI\Tools\Toolkits\AbstractToolkit;

class CalculatorToolkit extends AbstractToolkit
{
    public function guidelines(): ?string
    {
        return "This toolkit allows you to perform mathematical operations. You can also use this functions to solve
        mathematical expressions executing smaller operations step by step to calculate the final result.";
    }

    public function provide(): array
    {
        return [
            SumTool::make(),
            SubtractTool::make(),
            MultiplyTool::make(),
            DivideTool::make(),
            ExponentiateTool::make(),
        ];
    }
}
```

The `AbstractToolkit` base class establishes a consistent interface that all toolkits inherit, ensuring predictable behavior across the framework.

**Guidelines**

The `guidelines()` method serves a particularly important function in agent development – it provides contextual information that helps the underlying language model understand not just what tools are available, but how they should be used together. In the case of the `CalculatorToolkit`, the guidelines explicitly suggest that complex mathematical expressions can be solved through step-by-step operations, guiding the agent toward effective problem-solving strategies.

**Provide**

The `provide()` method returns the array of tools included in the toolkit by default. When a toolkit is attached to an agent, the individual tools become available exactly as if they had been added separately, but without the cognitive overhead of managing multiple tool declarations.

### Filters

During development of complex agents, I've frequently encountered scenarios where a toolkit provides mostly the right functionality but includes tools that could lead to undesired behavior in specific contexts, or just need to be restricted and configured individually.

#### Exclude

The `exclude()` method addresses this challenge elegantly, allowing developers to attach comprehensive toolkits while maintaining fine-grained control over available capabilities. This becomes particularly useful when working with specialized agents that need specific capabilities but you want to reduce the probability of an agent mistake, and reduce tokens consumption.

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

The exclusion mechanism operates at the class level, using fully qualified class names to identify tools for removal.

#### Only

In the same way you can also use the method `only()` to request a sub-set of the available tools in the toolkit.

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

#### With

Following the same pattern you may need to retrieve an instance of a specific tool from the toolkit to change its settings. You can do this using the `with()` method. You can pass the fully qualified class name to declare what tool you want to retrieve, and the tool instance will be injected into the callback so you can change its settings and return it back.

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

From an extensibility perspective, the toolkit system opens remarkable opportunities for community contribution and ecosystem growth. The consistent interface means that third-party developers can create domain-specific toolkits that integrate seamlessly with Neuron's architecture. A developer building agents for financial applications might create a FinancialToolkit that includes tools for currency conversion, interest calculation, and risk assessment. Similarly, a WebScrapingToolkit could package HTTP request tools, HTML parsing capabilities, and data extraction utilities into a single, reusable component.

## Available Toolkits

Neuron ships with several built-in tools and toolkits that allows you to quickly equip your agents with many skills. You can use these tools individually or attach entire toolkits with a single line of code.

### Calculator

The CalculatorToolkit provides a comprehensive suite of computational tools designed to make your AI agents performs accurate calculations. It can seamlessly integrates with complementary toolkits that provide data access—such as database connectors, CSV processors, API clients, or spreadsheet readers—enabling AI agents to perform sophisticated statistical calculations, and deliver comprehensive insights in response to complex business queries.

```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>sum</td><td>NeuronAI\Tools\Toolkits\Calculator\SumTool</td></tr><tr><td>subtract</td><td>NeuronAI\Tools\Toolkits\Calculator\SubtractTool</td></tr><tr><td>multiply</td><td>NeuronAI\Tools\Toolkits\Calculator\MultiplyTool</td></tr><tr><td>divide</td><td>NeuronAI\Tools\Toolkits\Calculator\DivideTool</td></tr><tr><td>exponential</td><td>NeuronAI\Tools\Toolkits\Calculator\ExponentialTool</td></tr><tr><td>square root</td><td>NeuronAI\Tools\Toolkits\Calculator\SquareRootTool</td></tr><tr><td>nth root</td><td>NeuronAI\Tools\Toolkits\Calculator\NthRootTool</td></tr><tr><td>mean</td><td>NeuronAI\Tools\Toolkits\Calculator\MeanTool</td></tr><tr><td>median</td><td>NeuronAI\Tools\Toolkits\Calculator\MedianTool</td></tr><tr><td>mode</td><td>NeuronAI\Tools\Toolkits\Calculator\ModeTool</td></tr><tr><td>standard deviation</td><td>NeuronAI\Tools\Toolkits\Calculator\StandardDeviationTool</td></tr><tr><td>variance</td><td>NeuronAI\Tools\Toolkits\Calculator\VarianceTool</td></tr></tbody></table>

### Calendar

​This toolkit provides comprehensive date and time operations. Use these tools to make your agent able to work with dates, times, formatting, calculations, and timezone conversions.

```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>current_datetime</td><td>NeuronAI\Tools\Toolkits\Calendar\CurrentDateTimeTool</td></tr><tr><td>get_timestamp</td><td>NeuronAI\Tools\Toolkits\Calendar\GetTimestampTool</td></tr><tr><td>format_date</td><td>NeuronAI\Tools\Toolkits\Calendar\FormatDateTool</td></tr><tr><td>date_difference</td><td>NeuronAI\Tools\Toolkits\Calendar\DateDifferenceTool</td></tr><tr><td>add_time</td><td>NeuronAI\Tools\Toolkits\Calendar\AddTimeTool</td></tr><tr><td>subtract_time</td><td>NeuronAI\Tools\Toolkits\Calendar\SubtractTimeTool</td></tr><tr><td>calculate_age</td><td>NeuronAI\Tools\Toolkits\Calendar\CalculateAgeTool</td></tr><tr><td>convert_timezone</td><td>NeuronAI\Tools\Toolkits\Calendar\ConvertTimezoneTool</td></tr><tr><td>get_timezone_info</td><td>NeuronAI\Tools\Toolkits\Calendar\GetTimezoneInfoTool</td></tr><tr><td>get_weekday</td><td>NeuronAI\Tools\Toolkits\Calendar\GetWeekdayTool</td></tr><tr><td>is_weekend</td><td>NeuronAI\Tools\Toolkits\Calendar\IsWeekendTool</td></tr><tr><td>is_leap_year</td><td>NeuronAI\Tools\Toolkits\Calendar\IsLeapYearTool</td></tr><tr><td>get_days_in_month</td><td>NeuronAI\Tools\Toolkits\Calendar\GetDaysInMonthTool</td></tr><tr><td>start_of_period</td><td>NeuronAI\Tools\Toolkits\Calendar\StartOfPeriodTool</td></tr><tr><td>end_of_period</td><td>NeuronAI\Tools\Toolkits\Calendar\EndOfPeriodTool</td></tr><tr><td>get_week_number</td><td>NeuronAI\Tools\Toolkits\Calendar\GetWeekNumberTool</td></tr><tr><td>compare_dates</td><td>NeuronAI\Tools\Toolkits\Calendar\CompareDatesTool</td></tr><tr><td>is_date_in_range</td><td>NeuronAI\Tools\Toolkits\Calendar\IsDateInRangeTool</td></tr></tbody></table>

### MySQL & PostgreSQL

These toolkits make your agent able to interact with your database. If you ask "How many votes did the authors get in the last 14 days?", the agent doesn’t guess or hallucinate an answer. Instead, it recognizes that this question requires database access, identifies the appropriate tables involved and retrieves real data from your system.

<figure><img src="/files/QXqLeOpSQGxT99N8W64v" alt=""><figcaption></figcaption></figure>

All the tools in the MySQL and PostgreSQL toolkits require a [PDO](https://www.php.net/manual/en/class.pdo.php) instance as a constructor argument. If you are in a framework environment or you are already using an ORM in general, you can gather the underlying PDO instance from the ORM and pass it to the tools. You can learn more about this implementation strategy in this in-depth article: <https://inspector.dev/mysql-ai-toolkit-bringing-intelligence-to-your-database-layer-in-php/>

The PDO instance is basically a connection to a specific database, so you could aslo think to create dedicated credentials for your agent. It could be helpful to control the level of access your agent has to the database.

Anyway you have separate tools for reading and writing to the database. If you are not confident about your agent behaviour you may not provide the writing tool.

```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 [
            // Connect to a MySQL database
            MySQLToolkit::make(
                new \PDO("mysql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            ),
            
            // or Postgre database
            PGSQLToolkit::make(
                new \PDO("pgsql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            ),
        ];
    }
}
```

{% hint style="warning" %}
These examples refer to the `MySQLToolkit` but it's exactly the same using `PGSQLToolkit`.
{% endhint %}

#### MySQLSchemaTool / PGSQLSchemaTool

This tool allows agents to understand the structure of your database, enabling them to construct intelligent queries without requiring you to hardcode table structures or relationships into prompts. This tool essentially gives your agent the equivalent of a database administrator’s understanding of your schema, allowing it to craft queries that respect your data model and take advantage of existing indexes and relationships.

```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(...)),
        ];
    }
}
```

This tool also accept a second argument `$tables`. You can basically pass a list of tables that you want to include in the schema information passed to the LLM. This is basically a way to limit the scope of the queries the agent will later execute on the database.

```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']
            ),
        ];
    }
}
```

By limiting the schema scope, you can create specialized agents that focus on specific areas of your application. A content management agent might only need access to articles, categories, and tags, while a user administration agent requires visibility into users, roles, and permissions tables. This approach not only improves performance but also reduces the cognitive load on the language model, leading to more accurate and focused responses.

#### MySQLSelectTool / PGSQLSelectTool

Use this tool to make your agent able to run SELECT query against the database.

```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

Use this tool to make your agent able to performs write operations against the database (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(...)),
        ];
    }
}
```

### FileSystem

This toolkit makes the agent able to interact with the local filesystem.

```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>describe_directory_content</td><td>NeuronAI\Tools\Toolkits\FileSystem\DescribeDirectoryContentTool</td></tr><tr><td>read_file</td><td>NeuronAI\Tools\Toolkits\FileSystem\ReadFileTool</td></tr><tr><td>grep_file_content</td><td>NeuronAI\Tools\Toolkits\FileSystem\GrepFileContentTool</td></tr><tr><td>glob_path</td><td>NeuronAI\Tools\Toolkits\FileSystem\GlobPathTool</td></tr><tr><td>preview_file</td><td>NeuronAI\Tools\Toolkits\FileSystem\PreviewFileTool</td></tr><tr><td>parse_file</td><td>NeuronAI\Tools\Toolkits\FileSystem\ParseFileTool</td></tr></tbody></table>

### Tavily

This toolkit enable your agent to performs web search, page content extraction, and crawling.

```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 Web Search

It makes your Agent able to search the web. It requires access to [Tavily APIs](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'
            ),
        ];
    }
}
```

You can customize the default options to retrieve search results by passing your preference in the `withOptions` method:

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

#### Tavily Extract

Extract web page content from an URL. It requires access to [Tavily APIs](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 Crawl

Tavily Crawl is a graph-based website traversal tool that can explore hundreds of paths in parallel with built-in extraction and intelligent discovery.

```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

This toolkit enable your agent to performs web search, and read the content of a specific 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 Web Search

It makes your Agent able to search the web. It requires access to [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 Reader

Extract web page content from an URL. It requires access to [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'
            ),
        ];
    }
}
```

### Zep Memory

This toolkit connects a NeuronAI Agent to [Zep](https://www.getzep.com/) knowledge graph. This kind of system allows the agent to store relevant facts that may emerge during interactions with the agent over time. It's a long term memory in the sense that is not limited to the current conversation like the [ChatHistory](/agent/chat-history-and-memory) component does. It's an external persistent storage the agent will use to store and retrieve single pieces of information that can allow more personalized answers.

To learn more about the capabilities of these kind of system you can visit the Zep website: <https://www.getzep.com/>

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Zep\ZepLongTermMemoryToolkit;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            ZepLongTermMemoryToolkit::make(
                key: 'ZEP_API_KEY',
                user_id: 'ID'
            ),
        ];
    }
}
```

The `user_id` arguments allows you to separate the long term memory in different silos if you want to serve multiple users. Based on your use case you can use this parameter as a "key" to separate the memory for the various entities the agent interact to (users, companies, etc.).

### AWS SES

#### Simple Email Service (SES)

This tool allows the agent to send an email message to one or more recipients, send notifications, confirmations, reports, or any other email-based communication. The tool handles proper email delivery, and basic error handling automatically.

In order ti use this tool the AWS sdk for PHP must be installed.

```
composer require aws/aws-sdk-php
```

The tool gets an instance of the `SesClient` class from the AWS PHP sdk.

```php
namespace App\Neuron;

use Aws\Ses\SesClient;
use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\AWS\SESTool;

class MyAgent extends Agent
{
    ...
    
    protected function tools(): array
    {
        return [
            SESTool::make(
                sesClient: new SesCleint(...),
                fromEmail: 'my-address@email.com'
            ),
        ];
    }
}
```

### Supadata YouTube

This toolkit provides access to YouTube video transcriptions, metadata, channel information,\
and playlist data through Supadata.ai for content analysis and research purposes.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataYouTubeToolkit;

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

#### Video Transcription

Allow the agent to retrieve the transcription of a youtube video.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataVideoTranscriptTool;

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

#### Video Metadata

Allow the agent to retrieve the metadata of a youtube video.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataVideoMetadataTool;

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

#### Channel Metadata

Allow the agent to retrieve metadata from a YouTube channel including name, description, subscriber count, and more.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataYoutubeChannelTool;

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

#### Playlist Metadata

Allow the agent to retrieve metadata from a YouTube playlist including title, description, video count, and more.

```php
namespace App\Neuron;

use NeuronAI\Agent;
use NeuronAI\Tools\Toolkits\Supadata\SupadataYoutubePlaylistTool;

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

## Parallel Tool Calls

If your agents are tool-hungry, you can enable parallel execution if the model ask for multiple tool calls in a single request.

#### Sequential Execution (Standard)

The agent calls tools **one at a time**, waiting for each to complete before starting the next:

```
1. Call tool A → wait for result
2. Call tool B → wait for result  
3. Call tool C → wait for result

Total time: Time(A) + Time(B) + Time(C)
```

#### Parallel Execution (With `pcntl`)

The agent calls **multiple tools simultaneously**, letting them run at the same time:

```
1. Call tool A, B, and C all at once
2. Wait for all to complete

Total time: Max(Time(A), Time(B), Time(C))
```

### Requirements

To use this feature you need to install the `spatie/fork` package. For more information check out the GitHub repository: <https://github.com/spatie/fork>

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

{% hint style="warning" %}

### Limitations

This implementation requires the `pcntl` extension which is installed in many Unix and Mac systems by default.

**pcntl only works in CLI processes, not in a web context.**

If the `pcntl` extension is not present in the system running the agent (e.g. Windows machines) the trait automatically fallbacks to the standard tool calls execution. This can be helpful if you have a missmatch between your local development environment and the production environment. You can develop locally with `pcntl` disabled, then deploy to production environments where it may be enabled—**without modifying a single line of code**. The agent adapts automatically to whatever execution environment it finds itself in.
{% endhint %}

### Enable parallel execution

Set `parallelToolCalls(true)` in your Agent or RAG. The framework will inject the dedicated node `ParallelToolNode` instead of the standard `ToolNode` in the workflow.

```php
class DemoAgent extends Agent
{
    public function __construct()
    {
        parent::__construct();
        $this->parallelToolCalls(true);
    }
    
    protected function provider(): AIProviderInterface
    {
        ...
    }

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

## Error Handler

Now the question is how to handle Tool errors. There are a couple of options, to fit different scenarios and needs.

The `ToolNode` accepts an `$errorHandler` argument [(code)](https://github.com/neuron-core/neuron-ai/blob/3.x/src/Agent/Nodes/ToolNode.php#L37). It's a callback that receives the exception being thrown by the tool, and the instance of the failing tool.

It allows you to implement a custom logic in case of tool error (General tool exceptions, or `ToolRunsExceededException`). **If you return a value it will be returned to the model as the result of the tool.** By default the ToolNode re-raise execution errors.

**Fluent definition:**

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

**Extending the Agent**

You can also implement `resolveToolErrorHandler()` directly to define the callback to run.

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

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


# Chat History

Learn how Neuron AI manage multi turn conversations.

Neuron AI provides you with a built-in system to manage the memory of a chat session you perform with the agent.

In many Q\&A applications you can have a back-and-forth conversation with the LLM, meaning the application needs some sort of "memory" of past questions and answers, and some logic for incorporating those into its current thinking.

For example, if you ask a follow-up question like "Can you elaborate on the second point?", this cannot be understood without the context of the previous messages.

In the example below you can see how the Agent doesn't know my name initially:

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

$message = Agent::make()
    ->chat(new UserMessage("What's my name?"))
    ->getMessage();

echo $message->getContent();
// I'm sorry I don't know your name. Do you want to tell me more about yourself?
```

Clearly the Agent doesn't have any context about me. Now I try present me in the first message, and then ask for my name:

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

$agent = Agent::make()

$message = $agent->chat(new UserMessage("Hi, my name is Valerio!"))->getMessage();
echo $message->getContent();
// Hi Valerio, nice to meet you, how can I help you today?

$message = $agent->chat(new UserMessage("Do you remember my name?"))->getMessage();
echo $message->getContent();
// Sure, your name is Valerio!
```

## How Chat History works

Neuron Agent takes the list of messages exchanged between your application and the LLM into an object called Chat History. It's a crucial part of the framework because the chat history needs to be managed based on the context window of the underlying LLM.

It's important to send past messages back to LLM to keep the context of the conversation, but if the list of messages grows enough to exceed the context window of the model the request will be rejected by the AI provider, because it exceeds the maximum capability of the LLM.

Chat history automatically truncates the list of messages to never exceed the context window avoiding unexpected errors. You may want to consider implementing more sophisticated context management strategies, like [summarization](/agent/middleware#summarization).

While cutting, the chat history tries to minimize the context loss. The internal trimmer can identify a cutting point slightly less aggressive than the initially identified. So, to make sure the agent conversation stays in the limit, **you should configure the context window in the agent chat history with a margin of 5%-10% from the actual limit of the underlying model**.

If your model works with a 200K context window, you should instantiate your chat history with 190K for example.

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\InMemoryChatHistory;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        ...
    }
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new InMemoryChatHistory(
            contextWindow: 190000
        );
    }
}
```

## How to feed a previous conversation

Sometimes you already have a representation of user to assistant conversation and you need a way to feed the agent with previous messages.

You can just pass an array of messages to the `chat()` method. This conversation will be automatically loaded into the agent memory and you can continue to iterate on it.

```php
use NeuronAI\Chat\Enums\MessageRole;
use NeuronAI\Chat\Messages\Message;

$message = MyAgent::make()
    ->chat([
        new Message(MessageRole::USER, "Hi, my company is called Inspector.dev"),
        new Message(MessageRole::ASSISTANT, "Great, how can I assist you today?"),
        new Message(MessageRole::USER, "What's the name of the company I work for?"),
    ])
    ->getMessage();
    
echo $message->getContent();
// You work for Inspector.dev
```

The last message in the list will be considered the most recent.

## Register the chat history

By default Neuron Agent uses an "in memory" chat history. That means it keeps messages only for the current execution cycle. But, if you want to persist messages across sessions you can tell the agent to use a different component by implementing the `chatHistory` method in the Agent class.

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\InMemoryChatHistory;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        ...
    }
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new InMemoryChatHistory(
            contextWindow: 50000
        );
    }
}
```

## Available Chat History Implementations

### InMemoryChatHistory

It simply store the list of messages into an array. It is kept in memory only during the current execution. It's used by default if you don't explicitly register another component.

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\InMemoryChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new InMemoryChatHistory(
            contextWindow: 150000
        );
    }
}
```

### FileChatHistory

This compnent makes you able to persist the ongoing conversation with the agent in a file, and resume it later in time. To create an instance of the `FileChatHistory` you need to pass the absolute path of the `directory` where you want to store conversations, and the unique `key` for the current conversation.

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\FileChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new FileChatHistory(
            directory: '/home/app/storage/neuron',
            key: 'THREAD_ID',
            contextWindow: 150000
        );
    }
}
```

The `key` parameter allows you to store different files to separate conversations. You can use a unique key for each user, or the ID of a thread to make users able to store multiple conversations.

### SQLChatHistory

This component allows you to store the ongoing conversation into a SQL database. Before using this component you must create the table on your database to store messages. Here is the SQL script:

```sql
CREATE TABLE IF NOT EXISTS chat_history (
  id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  thread_id VARCHAR(255) NOT NULL,
  messages LONGTEXT NOT NULL,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
 
  UNIQUE KEY uk_thread_id (thread_id),
  INDEX idx_thread_id (thread_id)
);
```

You can customize this table addind more columns eventually to add a relation to your users or similar use cases. You can also customize the table name passing your custom one when creating the instance.

To create an instance of the `SQLChatHistory` you need to pass the `thread_id` to separate different conversation threads, and the `PDO` connection to the database.

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: 'THREAD_ID',
            pdo: new \PDO("mysql:host=localhost;dbname=DB_NAME;charset=utf8mb4", "DB_USER", "DB_PASS"),
            table: 'chat_history',
            contextWindow: 150000
        );
    }
}
```

If your application is built on top of a framewrok you can easily get the PDO connection from the ORM. Here are is couple of examples in the context of Laravel or Symfony applications.

#### Laravel

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: 'CHAT_THREAD_ID',
            pdo: \DB::connection()->getPdo(),
            table: 'chat_history',
            contextWindow: 150000
        );
    }
}
```

#### Symfony

You can register your agent as a service with an instance of `Doctrine\DBAL\Connection` as a constructor dependency:

```php
namespace App\Neuron;

use Doctrine\DBAL\Connection;
use NeuronAI\Agent\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\SQLChatHistory;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    public function __construct(protected Connection $connection)
    {
        parent::__construct();
    }
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new SQLChatHistory(
            thread_id: 'CHAT_THREAD_ID',
            pdo: $this->connection->getNativeConnection(),
            table: 'chat_history',
            contextWindow: 150000
        );
    }
}
```

### EloquentChatHistory

You should create your own Eloquent model and pass the class string as the constructor argument. The model can have custom relations, scopes, attributes, etc. but the basic structure must be based on this migration script:

```bash
php artisan make:migration create_chat_messages_table --create=chat_messages
```

```php
Schema::create('chat_messages', function (Blueprint $table) {
     $table->id();
     $table->string('thread_id')->index();
     $table->string('role');
     $table->json('content');
     $table->json('meta')->nullable();
     $table->timestamps();

     $table->index(['thread_id', 'id']); // For efficient ordering and trimming
});
```

#### ChatMessage model example

```php
class ChatMessage extends Model
{
    protected $fillable = [
        'thread_id', 'role', 'content', 'meta'
    ];
    
    protected $casts = [
        'content' => 'array', 
        'meta' => 'array'
    ];
    
    /**
     * return BelongsTo<Conversation, $this>
     */
    public function conversation(): BelongsTo
    {
        return $this->belongsTo(Conversation::class, 'thread_id');
    }
}
```

Use in your agent:

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\History\ChatHistoryInterface;
use NeuronAI\Chat\History\EloquentChatHistory;

class MyAgent extends Agent
{
    ...
    
    protected function chatHistory(): ChatHistoryInterface
    {
        return new EloquentChatHistory(
            thread_id: 'THREAD_ID',
            modelClass: ChatMessage::class,
            contextWindow: 150000
        );
    }
}
```

## Implement custom chat history

You can create a custom implementation of the chat history to support different persistent layer just implementing `AbstractChatHistory`. It allows you to inherit several behaviors for the internal history management, so you have just to implement a couple of methods to save messages into the storage system you want to use.

```php
abstract class AbstractChatHistory implements ChatHistoryInterface
{
    /**
     * @param Message[] $messages
     */
    protected function setMessages(array $messages): void
    {
        // Handle saving the entire history at once every time the history is updated.
    }

    protected function onNewMessage(Message $message): void
    {
        // Handle single message addition
    }

    protected function onTrimHistory(int $index): void
    {
        // When the trim is triggered, 
        // the messages in the position from zero to $index must be removed.
    }

    protected function clear(): void
    {
        // Remove all messages.
    }
}
```

The abstract class already implement some utility methods to calculate tokens usage based on the AI provider responses and automatically cut the conversation based on the size of the context window. You just have to focus on the interaction with the underlying storage to add and remove messages, or clear the entire history.

We strongly suggest to look at other implementations like `FileChatHistory` to understand how to create your own.

### Serialize/Deserialize Messages

When the ChatHistory needs to store a message it must be serialized. The same way, when the ChatHistory component is instantiated it should load all the previous messages from the underlying storage (database, cache, etc) and deserialize them to the original message type.

To serialize/deserialize messages consistently the `AbstractChatHistory` provides you with `serializeMessage()` and `deserializeMessage()` methods. Here is an example of how to use them in an hypothetical database chat history implementation:

```php
<?php

namespace NeuronAI\Chat\History;

use NeuronAI\Chat\Messages\Message;

class DatabaseChatHistory extends AbstractChatHistory
{
    public function __construct(protected \PDO $db) 
    {
        // Retrieve the current conversation from the underlying storage
        $messages = $this->db->select(...);
        
        // Deserialize properly initialize the correct message types with the correct data.
        $this->history = $this->deserializeMessages($messages);
    }

    protected function onNewMessage(Message $message): void
    {
        // Store the serialized version.
        $this->db->insert($message->jsonSerialize());
    }

    ...
}
```


# Streaming

Presenting AI response to your user in real-time.

Streaming enables you to show users chunks of response text as they arrive rather than blindly waiting for the full response. You can offer a real-time Agent conversation experience.

<figure><img src="/files/b2ldC0sehofX9NeBePUB" alt=""><figcaption></figcaption></figure>

### Agent

To stream the AI response you should use the `stream()` method on the agent, instead of `chat()`. This method prepares the agent workflow to use the `StreamingNode` instead of `ChatNode`.

Calling the `events()` method on the returning agent handler you get a PHP generator that can be used to consume the streamiong as an iterable object.

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Messages\UserMessage;

$handler = MyAgent::make()->stream(new UserMessage('How are you?'));

// Print the response chunk-by-chunk in real-time
foreach ($handler->events() as $chunk) {
    echo $chunk->content;
}

// I'm fine, thank you! How can I assist you today?
```

### Streaming chunks

When you process the streamed response of the agent you can expect to receive three types of chunk objects:

* `TextChunk`: represents a piece of text
* `ReasoningChunk`: contains chunks of the reasoning summary of the model (only available for reasoning models)
* `ToolCallChunk`: represents the LLM asking for a tool execution
* `ToolResultChunk`: contains the results of tool execution

These objects are a layer of abstraction between the underlying messages flow inside the agent to perform a task and the data needed on the client side to stay informed on what's going on behind the scenes.

The stream composition depends by your agent implementation. If the agent has no tools attached there is no chance to receive a `ToolCallChunk` or `ToolResultChunk` instance, so you can iterate the output stream expecting only text and reasoning chunks.

### Streaming & Tools

Neuron support Tools & Function calls in combination with the streaming response. You are free to provide your Agents with Tools and they will be automatically handled in the middle of the stream, to continue toward the final response.

When the agent receive a tool call request from the LLM, it will stream two types of chunk: `ToolCallChunk`, `ToolResultChunk`.

These classes contain the instance of the tool behind called by the LLM so you can expose informative output to the client about what the agent is doind to answer the user prompt.

Here is an example of how you can deal with this scenario:

```php
use App\Neuron\MyAgent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Tools\Tool;

$handler = MyAgent::make()
    ->addTool(
        Tool::make(
            'get_server_configuration',
            'retrieve the server network configuration'
        )->addProperty(...)->setCallable(...)
    )
    ->stream(
        new UserMessage("What's the IP address of the server?")
    );

// Iterate chunks
foreach ($handler->events() as $chunk) {
    if ($chunk instanceof ToolCallChunk) {
        // Output the ongoing tool call
        echo "\n- Calling tool: ".$chunk->tool->getName();
        echo "\n- Input: ".json_encode($chunk->tool->getInputs());
        continue;
    }
    
    if ($chunk instanceof ToolResultChunk) {
        echo "\n- Tool ".$chunk->tool->getName()." completed";
        echo "\n- Result: ".$chunk->tool->getResult();
        continue;
    }
    
    // Handle TextChunk and ReasoningChunk
    echo $chunk->content;
}

// Let me retrieve the server configuration. 
// - Calling tool: get_server_configuration
// - Tool get_server_configuration completed
// The IP address of the server is: 192.168.0.10
```

### Get The Final Result

When the model finishes streaming output you can retrieve the final `AssistantMessage` instance with the `getMessage()` method on the workflow handler:

```php
$handler = MyAgent::make()->stream(...);

// Iterate chunks
foreach ($handler->events() as $chunk) {
    // ...
}

$message = $handler->getMessage(); // Get the final message instance
echo $message->getContent();
```

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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

## Stream Adapters

Neuron's Stream Adapter system provides a flexible, protocol-agnostic way to help you easily integrate Neuron powered agents with your frontend stack.

Stream adapters act as translators between Neuron's internal streaming events (text chunks, tool calls, reasoning steps) and specific frontend protocols like Vercel AI SDK or AG-UI.

You can also plug in adapters to send streamed data to an external transport layer like [Pusher](https://pusher.com/), if you want to stream contents to the UI from agent executed in the background.

This architecture allows you to seamlessly integrate Neuron agents with various frontend frameworks without modifying your core agent logic. Adapters handle protocol-specific concerns such as message lifecycle events, event formatting, and ID tracking, while maintaining consistent streaming behavior across all providers (Anthropic, OpenAI, Gemini, Ollama, etc.). The system is highly extensible, you can create custom adapters by extending `SSEAdapter` to implement streaming data transofrmations, or directly implement the `StreamAdapterInterface` for custom needs.

<figure><img src="/files/L02GGuYBoNrkOZqUAtNU" alt=""><figcaption></figcaption></figure>

You simply need to provide an adapter instance to the `events()` method of the agent handler used to stream the LLM response.

### AG-UI Adapter

Implements the streaming event-based protocol defined by AG-UI protocol for real-time agent-frontend interaction. Supports text messages, tool calls, reasoning, and lifecycle events.

For more information, visit: <https://docs.ag-ui.com/concepts/events>

```php
use NeuronAI\Chat\Messages\Stream\Adapters\AGUIAdapter;

// Instruct the agent
$handler = MyAgent::make()
    ->stream(
        new UserMessage('What is the square root of 144?')
    );

// Provide the adapter instance to the events() method
$stream = $handler->events(new AGUIAdapter());

// Process the response
foreach ($stream as $line) {
    echo $line;
}
```

#### Connecting an AG-UI frontend

An AG-UI client (like CopilotKit) does not just open a connection. It sends a POST request with a JSON body called `RunAgentInput`, containing the conversation and the identifiers of the current run:

```json
{
  "threadId": "thread_123",
  "runId": "run_456",
  "messages": [
    {
      "id": "msg_1",
      "role": "user",
      "content": "What is the square root of 144?"
    }
  ],
  "tools": [],
  "state": {},
  "context": [],
  "forwardedProps": {}
}
```

Your endpoint should read this payload, map the messages to Neuron message objects, and pass `threadId` and `runId` to the adapter constructor. The adapter echoes them back in the `RUN_STARTED` and `RUN_FINISHED` events, so the client can correlate the stream with the run it requested. If you omit them, the adapter generates its own identifiers (useful for testing, but a real AG-UI frontend expects its own IDs back).

The adapter also provides the HTTP headers required by the SSE transport via the `getHeaders()` method. Remember to send them and to flush the output after each line, otherwise the stream can get stuck in PHP output buffers or proxies.

Here is a complete endpoint example:

```php
use NeuronAI\Chat\Messages\Stream\Adapters\AGUIAdapter;
use NeuronAI\Chat\Messages\UserMessage;

// Parse the AG-UI RunAgentInput payload
$input = json_decode(file_get_contents('php://input'), true);

$messages = [];
foreach ($input['messages'] as $message) {
    if ($message['role'] === 'user') {
        $messages[] = new UserMessage($message['content']);
    }
}

// Echo the client's thread and run identifiers back in the stream
$adapter = new AGUIAdapter(
    threadId: $input['threadId'],
    runId: $input['runId'],
);

// Send the SSE headers required by the protocol
foreach ($adapter->getHeaders() as $name => $value) {
    header("{$name}: {$value}");
}

$stream = MyAgent::make()->stream($messages)->events($adapter);

foreach ($stream as $line) {
    echo $line;
    flush();
}
```

#### Emitted events

The adapter translates Neuron streaming chunks into the following AG-UI events:

| Neuron chunk      | AG-UI events                                                                                                        |
| ----------------- | ------------------------------------------------------------------------------------------------------------------- |
| Run lifecycle     | `RUN_STARTED`, `RUN_FINISHED`                                                                                       |
| `TextChunk`       | `TEXT_MESSAGE_START`, `TEXT_MESSAGE_CONTENT`, `TEXT_MESSAGE_END`                                                    |
| `ReasoningChunk`  | `REASONING_START`, `REASONING_MESSAGE_START`, `REASONING_MESSAGE_CONTENT`, `REASONING_MESSAGE_END`, `REASONING_END` |
| `ToolCallChunk`   | `TOOL_CALL_START`, `TOOL_CALL_ARGS`, `TOOL_CALL_END`                                                                |
| `ToolResultChunk` | `TOOL_CALL_RESULT`                                                                                                  |

Tools attached to a Neuron agent are executed on the server. The client is informed of the ongoing execution through the `TOOL_CALL_*` events and receives the tool output in the `TOOL_CALL_RESULT` event, followed by the agent's final text message. The frontend-defined tools listed in the `tools` field of `RunAgentInput` (tools executed by the client) are not handled by the adapter.

The adapter does not emit the AG-UI shared state events (`STATE_SNAPSHOT`, `STATE_DELTA`, `MESSAGES_SNAPSHOT`), so state synchronization features of AG-UI clients are not available through this adapter.

### Vercel AI SDK Adapter

Adapter for Vercel AI SDK Data Stream Protocol: <https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol>

```php
use NeuronAI\Chat\Messages\Stream\Adapters\VercelAIAdapter;

// Instruct the agent
$handler = MyAgent::make()
    ->stream(
        new UserMessage('What is the square root of 144?')
    );

// Provide the adapter instance to the events() method
$stream = $handler->events(new VercelAIAdapter());

// Process the response
foreach ($stream as $line) {
    echo $line;
}
```

### Custom Adapters

The events() method of the agent handler accept an instance of StreamAdapterInterface. So you are free to implement this interface with custom implementation, and pass it to the handler. Here is how the interface looks like:

```php
interface StreamAdapterInterface
{
    /**
     * Transform a Neuron chunk into protocol-specific output.
     *
     * @param object $chunk Any Neuron chunk (TextChunk, ToolCallChunk, etc.)
     * @return iterable<string> One or more output lines/messages
     */
    public function transform(object $chunk): iterable;

    /**
     * Get HTTP headers for this protocol.
     *
     * @return array<string, string>
     */
    public function getHeaders(): array;

    /**
     * Protocol initialization sequence (optional).
     *
     * @return iterable<string>
     */
    public function start(): iterable;

    /**
     * Protocol termination sequence (optional).
     *
     * @return iterable<string>
     */
    public function end(): iterable;
}
```

You can always get inspiration by the built-in implementations.


# Structured Output

Enforce the Agent output based on the provided schema.

{% hint style="info" %}

### PREREQUISITES

This guide assumes you are already familiar with the following concepts:

* [Agent](/agent/agent)
* [Tool & Function Call](/agent/tools)
  {% endhint %}

There are many use cases where we need Agents to understand natural language, but output in a *structured format*. One common use-case is extracting data from text to insert into a database or use with some other downstream system. This guide covers how Neuron allows you to enforce structured outputs from the agent.

<figure><img src="/files/ay6HaG4cgkzAarsQaZHc" alt=""><figcaption></figcaption></figure>

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

### How to use Structured Output

The central concept is that the output structure of LLM responses needs to be represented in some way. The schema that Neuron validates against is defined by PHP type hints. Basically you have to define a class with strictly typed properties:

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;

class Person 
{
    #[SchemaProperty(
        description: 'The user name.', 
        required: true
    )]
    public string $name;
    
    #[SchemaProperty(
        description: 'What the user love to eat.', 
        required: false
    )]
    public string $preference;
}
```

Neuron generates the corresponding JSON schema from the PHP object to instruct the underlying model about your required data format. Then the agent parse the LLM output to extract data and returns an object instance filled with appropriate values:

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

// Talk to the agent requiring the structured output
$person = MyAgent::make()->structured(
    new UserMessage("I'm John and I like pizza!"),
    Person::class
);

echo $person->name.' like '.$person->preference;
// John like pizza
```

### Default output class

You can also encapsulate the output format into the Agent implementation, so it will be the Agent standard output format. You always need to call the `structured()` method to require strict output.

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

// Encapsulate the default output format 
class MyAgent extends Agent
{
    ...

    protected function getOutputClass(): string
    {
        return Person::class;
    }
}

// Always use the structured method if you want to get structured output
$person = MyAgent::make()
    ->structured(new UserMessage("I'm John and I like pizza"));

echo $person->name.' like '.$person->preference;
// John like pizza
```

### Control the output generation

Neuron requires you to define two layers of rules to create the structured output class.

The first is the `SchemaProperty` attribute that allows you to control the JSON schema sent to the LLM to understand the required data format.

The second layer is validation. Validation attributes will ensure data gathered from the LLM response are consistent with your requirements.

<figure><img src="/files/7QsgZugLMF1TyhHoDIII" alt=""><figcaption></figcaption></figure>

### SchemaProperty

The `SchemaProperty` attribute allows you to define the JSON schema parameters of each property:

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;

class Person 
{
    #[SchemaProperty(
        description: 'The user name.',
        required: true,
        minLength: 3,
        maxLength: 255,
    )]
    public string $name;
    
    #[SchemaProperty(
        description: 'What the user love to eat.', 
        required: false,
        min: 18,
        max: 64,
    )]
    public ?int $age = null;
}
```

### Nested Class

You can construct complex output structures using other PHP objects as a property type. Following the example of a the `Person` class we can add the `address` property typed as another structured class.

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Property;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Valid;

class Person 
{
    #[SchemaProperty(
        description: 'The user name.', 
        required: true
    )]
    #[NotBlank]
    public string $name;
    
    #[SchemaProperty(
        description: 'What user love to eat.', 
        required: true
    )]
    public string $preference;
    
    #[SchemaProperty(
        description: 'The address to complete the delivery.', 
        required: true
    )]
    public Address $address;
}
```

In the `Address` definition we require only the street and zip code properties, and allow city to be empty.

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Address
{
    #[SchemaProperty(
        description: 'The name of the street.', 
        required: true
    )]
    #[NotBlank]
    public string $street;

    #[SchemaProperty(
        description: 'The name of the city.', 
        required: false
    )]
    public string $city;

    #[SchemaProperty(
        description: 'The zip code of the address.', 
        required: true
    )]
    #[NotBlank]
    public string $zip;
}
```

Now when you ask the agent for the structured output you will get the filled instance back:

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

// Talk to the agent requiring the structured output
$person = MyAgent::make()->structured(
    new UserMessage("I'm John and I want a pizza at st. James Street 00560!"),
    Person::class
);

echo $person->name.' like '.$person->preference.'. Address: '.$person->address->street;
// John like pizza. Address: st.James Street
```

## Array

If you declare a property as an array, Neuron assumes the list of items to be a list of string. If you want the array to contains a list of other structured objects you can specify that using the `anyOf` argument:

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Person 
{
    #[SchemaProperty(
        description: 'The user name.', 
        required: true
    )]
    #[NotBlank]
    public string $name;
    
    #[SchemaProperty(
        description: 'The list of tag for the user profile.', 
        required: true,
        anyOf: [Tag::class]
    )]
    public array $tags;
}
```

And here is the hypotetical implementation of the `Tag` class with its own validation rules and property info:

```php
<?php

namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\SchemaProperty;
use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Tag
{
    #[SchemaProperty(
        description: 'The name of the tag', 
        required: true,
    )]
    #[NotBlank]
    public string $name;
}
```

#### Array with multiple types

As you can notice from the example above the anyOf argument is an array. Neuron also supports the composition of arrays with multiple object types. Just list the structured objects the array can contains and Neuron will include all of their specs in the JSON schema for the LLM.

```php
class Report
{
    #[SchemaProperty(
        description: 'The content of the report', 
        required: true,
        anyOf: [TextBlock::class, TableBlock::class, ImageBlock::class]
    )]
    public array $content;
}
```

## Max Retries

Since the LLM are not perfectly deterministic it's mandatory to have a retry mechanism in place if something is missing in the LLM response.

By default Neuron extracts and validates the data from the LLM response and if there is one or more validation errors automatically retry the request just one more time informing the LLM about what went wrong and for what properties.

You can eventually customize the number of times the agent must retry to get a correct answer from the LLM:

```php
$person = MyAgent::make()->structured(
    messages: new UserMessage("I'm John and I like pizza!"),
    class: Person::class,
    maxRetries: 3
);
```

If you work with a less capable LLM consider to use a number of retries balancing the probability to get e valid answer, and the potential token consumption.

You can disable retry just passing zero. It will be a one shot attempt:

```php
$person = MyAgent::make()->structured(
    messages: new UserMessage("I'm John and I like pizza!"),
    class: Person::class,
    maxRetries: 0
);
```

## Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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

<figure><img src="/files/ocFjYUpxzm7KiWQnL2HF" alt=""><figcaption></figcaption></figure>

Each segment bring its own debug information to follow the agent execution in real time:

<figure><img src="/files/KYNroOaw04zEESiBGUtP" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Learn how to enable [**observability**](/agent/observability) in the next section.
{% endhint %}

## Validation Rules

Since LLMs are not-deterministic systems and the consistency of their output can be highly influenced by the quality of the context they have as input, and they can still hallucinate, we provide a set of validation rules you can add to structured class properties to instruct Neuron verify the final set of data generated by the LLM.

Validation rules allows Neuron to resend the generation request to the LLM multiple times with a detailed report of what was wrong if one or more properties are not valid, until it reaches the [maxRetries](#max-retries) value.

If you don't define any validation rule, the data extracted from the LLM response will be filled into the structured output class directly.

### #\[NotBlank]

The property under validation cannot be blank. It accept the `allowNull` flag to treat explicitly null value as empty equivalent or not.

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\NotBlank;

class Person 
{
    #[NotBlank(allowNull: false)]
    public string $name;
}
```

### #\[Length]

Determine if the length of a `string` match the given criteria:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Length;

class Person 
{
    #[Length(min: 1, max: 10)]
    public string $name;
    
    #[Length(exactly: 5)]
    public string $zip_code;
}
```

### #\[WordsCount]

Determine if the number of words in a `string` match the given criteria:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\WordsCount;

class Person 
{
    #[WordsCount(exactly: 10)]
    public string $title;
    
    #[WordsCount(min: 1, max: 10)]
    public string $content;
}
```

### #\[Count]

Determine if the size of an `array` match the given criteria:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Count;

class Person 
{
    #[Count(min: 1, max: 3)]
    public array $dogs;
    
    #[Count(exactly: 1)]
    public array $children;
}
```

### #\[EqualTo] - #\[NotEqualTo]

These rules have the same structure and meaning, and accept a single argument to define the value to compare against. The property under validation must be strictly equal (*#\[EqualTo]*) or different (*#\[NotEqualTo]*) than the reference value:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\EqualTo;
use NeuronAI\StructuredOutput\Validation\Rules\NotEqualTo;

class Person 
{
    #[EqualTo(reference: 'Rome')]
    public string $city;
    
    #[NotEqualTo(reference: '00502')]
    public string $zip_code;
}
```

### #\[GreaterThan] - #\[GreaterThanEqual]

These rules have the same structure and meaning, and accept a single argument to define the value to compare against. The property under validation must be strictly greater (*#\[GreaterThan]*) or equal (*#\[GreaterThanEqual]*) than the reference value:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\GreaterThan;
use NeuronAI\StructuredOutput\Validation\Rules\GreaterThanEqual;

class Person 
{
    #[GreaterThan(reference: 17)]
    public int $age;
    
    #[GreaterThanEqual(reference: 1)]
    public int $cars;
}
```

### #\[LowerThan] - #\[LowerThanEqual]

These rules have the same structure and meaning, and accept a single argument to define the value to compare against. The property under validation must be strictly lower (*#\[LowerThan]*) or equal (*#\[LowerThanEqual]*) than the reference value:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\LowerThan;
use NeuronAI\StructuredOutput\Validation\Rules\LowerThanEqual;

class Person 
{
    #[LowerThan(reference: 50)]
    public int $age;
    
    #[LowerThanEqual(reference: 1)]
    public int $cars;
}
```

### #\[OutOfRange]

Determin if a `number` is out of the given range:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\InRange;

class Person 
{
    #[OutOfRange(min: 18, max: 35)]
    public int $age;
    
    // The strict argument force to stay stricly out of the range limits
    #[OutOfRange(min: 48, max: 54, strict: true)]
    public int $size;
}
```

### #\[IsFalse] - #\[IsTrue]

The property under validation must have exactly the boolean value defined by the rule:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\IsFalse;
use NeuronAI\StructuredOutput\Validation\Rules\IsTrue;

class Phone
{
    #[IsFalse]
    public bool $iphone;
    
    #[IsTrue]
    public bool $refurbed;
}
```

### #\[IsNull] - #\[IsNotNull]

The property under validation must respect the nullable condition defined by the rule:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\IsNotNull;
use NeuronAI\StructuredOutput\Validation\Rules\IsNull;

class Phone
{
    #[IsNotNull]
    public string $brand;
    
    #[IsNull]
    public ?string $test;
}
```

### #\[Json]

The property under validation must contains a valid JSON string:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Json;

class Person
{
    #[Json]
    public string $address;
}
```

### #\[Url]

The property under validation must contains a valid URL:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Url;

class Person
{
    #[Url]
    public string $website;
}
```

### #\[Email]

The property under validation must contains a valid Email address:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Email;

class Person
{
    #[Email]
    public string $email;
}
```

### #\[IpAddress]

The property under validation must contains a valid IP address:

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\IpAddress;

class Spec
{
    #[IpAddress]
    public string $ip;
}
```

### #\[ArrayOf]

The property under validation must be an array that contains all of the given type of object.

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\ArrayOf;

class Post
{
    #[ArrayOf(Tag::class)]
    public array $tags;
}
```

### #\[Regex]

The property under validation must respect the given regular expression.

```php
namespace App\Neuron\Output;

use NeuronAI\StructuredOutput\Validation\Rules\Regex;

class Coupon
{
    #[Regex('/^[A-Z]{2}\d{4}$/')]
    public string $code;
}
```

## Custom Validation Rules

Validation rules are PHP Attributes, so to create a new one you should extend the AbstractValidationRule of the framework and mark the class as a PHP Attribute:

```php
namespace App\Neuron\Output;

use Attribute;
use NeuronAI\StructuredOutput\Validation\Rules\AbstractValidationRule;

#[Attribute(Attribute::TARGET_PROPERTY)]
class MyFormatRule extends AbstractValidationRule
{
    public function __construct(protected string $format)
    {
    }

    public function validate(string $name, mixed $value, array &$violations): void
    {
        if (!is_string($value)) {
            $violations[] = $this->buildMessage($name, '{name} must be a string.');
        } else if (!$this->respectFormat($this->format, $value)) {
            $violations[] = $this->buildMessage(
                $name,
                '{name} must match the format {format}',
                ['format' => $this->pattern]
            );
        }
    }
    
    protected function respectFormat(string $value)
    {
        ...
    }
}
```

Now you can use the rule in your structured output class:

```php
namespace App\Neuron\Output;

class Route
{
    #[MyFormatRule('apps/{id}/show')]
    public string $path;
}
```


# MCP

Connect the tools provided by Model Context Protocol (MCP) servers to your agent.

MCP (Model Context Protocol) is an open source standard designed by Anthropic to connect your agents to external service providers, such as your application database or external APIs.

Thanks to this protocol you can make tools exposed by an external server available to your agent.

Companies can build MCP servers to allow developers to connect Agents to their platforms. Here are a couple of directories with most used MCP servers:

* MCP official GitHub - <https://github.com/modelcontextprotocol/servers>
* MCP-GET registry - <https://mcp-get.com/>

### How it works

Neuron provides you with the `McpConnector` class that you can instantiate passing the MCP server configuration.

```php
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    ...
    
    protected function tools(): array
    {
        return [
            ...McpConnector::make([
                'command' => 'php',
                'args' => ['/home/code/mcp_server.php'],
            ])->tools(),
        ];
    }
}
```

You should create an `McpConnector` instance for each MCP server you want to interact to.

Neuron automatically discovers the tools exposed by the server and connects them to your agent.

When the agent decides to run a tool, Neuron will generate the appropriate request to call the tool on the MCP servers and return the result to the LLM to continue the task. It feels exactly like with your own defined tools, but you can access a huge archive of predefined actions your agent can perform with just one line of code.

### Local MCP Server

If you want to connect with an MCP server installed locally on your machine or VM, you can use the "command" style configuration.

```php
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    ...
    
    protected function tools(): array
    {
        return [
            ...McpConnector::make([
                'command' => 'php',
                'args' => ['/home/code/mcp_server.php'],
            ])->tools(),
        ];
    }
}
```

## Remote MCP Server

### Streamable HTTP Server

Remote servers are accessible via URLs and typically require authentication. You can use the `token` field in the configuration array, which will be used as the authorization token to authenticate on the server:

```php
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    ...
    
    protected function tools(): array
    {
        return [
            ...McpConnector::make([
                'url' => 'https://mcp.example.com',
                'token' => 'BEARER_TOKEN',
                'timeout' => 30,
                'headers' => [
                    //'x-cutom-header' => 'value'
                ]
            ])->tools(),
        ];
    }
}
```

### SSE HTTP Transport

SSE ([Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)) is a mechanism that allows web clients to receive automatic updates from a server. Those updates are known as "events", and are sent over a single, long-lived HTTP connection.

To use the SSE transport you need to set `async ⇒ true` in the configuration parameters.

```php
use NeuronAI\MCP\McpConnector;

class MyAgent extends Agent 
{
    ...
    
    protected function tools(): array
    {
        return [
            ...McpConnector::make([
                'url' => 'https://mcp.example.com',
                'token' => 'BEARER_TOKEN',
                'timeout' => 30,
                'async' => true
            ])->tools(),
        ];
    }
}
```

## Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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

## Filter the list of tools

During connection with complex MCP servers they can includes tools that could lead to undesired behavior in specific contexts. The `exclude()` and `only()` methods address this challenge elegantly, allowing developers to connect with comprehensive MCP servers while maintaining fine-grained control over available capabilities you want to provide to your agent.

This becomes particularly useful when working with specialized agents that need specific capabilities but you want to reduce the probability of an agent mistake, and reduce tokens consumption.

These methods accept a list of tool names that you do or do not want to associate with the agent.

```php
class MyAgent extends Agent 
{
    ...
    
    protected function tools()
    {
        return [
            // EXCLUDE: discard certain tools
            ...McpConnector::make([
                'url' => 'https://mcp.example.com',
            ])->exclude([
                'tool_name_1',
                'tool_name_2',
            ])->tools(),
            
            // ONLY: Select the tools you want to include
            ...McpConnector::make([
                'url' => 'https://mcp.example.com',
            ])->only([
                'tool_name_1',
                'tool_name_2',
            ])->tools(),
        ];
    }
}
```


# Middleware

Interact with the agent execution flow to customize its behaviour.

Middleware is a feature of the basic Workflow component. So you can attach custom middleware also to Agent and RAG to hook into their execution cycle.

### The Agent Workflow

The Agent class is an extension of the Workflow component. The Workflow is the foundational piece of the puzzle in Neuron. Many features of the Agent and RAG components inherit their logic from the capabilities of the underlying Workflow.

Here is a simple schema of the workflow used to create the agent implementation:

<figure><img src="/files/sib93zb5GhSi0z0iWns5" alt=""><figcaption></figcaption></figure>

With this architecture in mind, you are free to use middleware to hook the agent workflow, interruption to keep humans in the loop, or look below for a set of built-in components we provide for common use cases.

### Tool Approval (Human In The Loop)

{% hint style="info" %}
Before using ToolApproval you should be familiar with the workflow [persistence](/workflow/persistence) and [interruption](/workflow/human-in-the-loop).
{% endhint %}

In Neuron, the Agent entity is built on top of the Workflow component. That means it can be interrupted to ask confirmation before performing critical actions. The `ToolApproval` middleware pause agent execution for human approval or rejection of tool calls before they execute.

```php
use NeuronAI\Agent\Agent;
use NeuronAI\Agent\Middleware\ToolApproval;
use NeuronAI\Workflow\Middleware\WorkflowMiddleware;
use NeuronAI\Workflow\NodeInterface;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {...}
    
    /**
     * Register tools
     */
    protected function tools(): array
    {
        return [
            BuyTicketTool::make(),
        ];
    }

    /**
     * Attach middleware to nodes.
     */
    protected function middleware(): array
    {
        return [
            ToolNode::class => [
                new ToolApproval(
                    // Provide a list of tool classes or names that need to be approved
                    tools: [BuyTicketTool::class]
                )
            ],
        ];
    }
}
```

Once the agent tries to call one of the listed tools in the `ToolApproval` middleware it fires the workflow interrutpion exception. You have to catch this exception, and present the user the UI to collect it's feedback. The interruption exception will contain an instance of `ApprovalRequest` with actions that require user feedback.

```php
use NeuronAI\Workflow\Interrupt\WorkflowInterrupt;
use NeuronAI\Workflow\Persistence\FilePersistence;

$persistence = new FilePersistence(__DIR__);

try {

    $response = new MyAgent($presistence)
        ->chat(new UserMessage("What's the weather like in Italy?"))
        ->getMessage();
        
} catch (WorkflowInterrupt $interrupt) {
    $approvalRequest = json_encode($interrupt->getRequest());
    $resumeToken = $interrupt->getResumeToken();
    
    // Store request and resumeToken to collect the user feedback, and restart the agent workflow later
}
```

The resume token is auto-generated and available in the interrupt exception.

You should store the `approval request` along with the `resume token` to restart the agent workflow later, exactly where it left off. You can use a database or any other persistence layer is convinient for your application. The approval request is json-serializable so you can easily put its structure into a store.

Once the user approved/rejected the actions, you can resume the agent feeding it the edited request.

```php
$persistence = new FilePersistence(__DIR__);

// Retreive request and token after user interaction to restart the workflow
$approvalRequest = ApprovalRequest::fromArray(...);
$resumeToken = ...

$response = new MyAgent($persistence, $resumeToken)
        ->chat(interrupt: $approvalRequest)
        ->getMessage();
```

To better understand how to manage the interrutpion flow you can check out this example:

{% embed url="<https://github.com/neuron-core/neuron-ai/blob/main/examples/agent/tool-approval.php>" %}

Or refer to the full [workflow documentation](/workflow/human-in-the-loop).

### Conditional approval

The example above it's a classic on/off approval flow. If a tool is listed in the `ToolApproval` middleware the agent will interrupt the execution, otherwise the tool will be executed as usual.

The middleware also accepts a callback associated to tools, in order to define your custom approval condition. The callback receives the tool's instance and returns `true` if the tool requires approval, or `false` to skip the interruption and run the tool as it is.

```php
class MyAgent extends Agent
{
    ...
    
    /**
     * Register tools
     */
    protected function tools(): array
    {
        return [
            BuyTicketTool::make(),
        ];
    }

    /**
     * Attach middleware to nodes.
     */
    protected function middleware(): array
    {
        return [
            ToolNode::class => [
                new ToolApproval(
                    tools: [
                        // Ask for approval if the amount is greather than 100
                        BuyTicketTool::class => function (array $args): bool {
                            return $args['amount'] > 100;
                        }
                    ]
                )
            ],
        ];
    }
}
```

In the exmple above we require the human approval only if the ticket costs more than 100, otherwise the callback return false, that means no need for interruption.

### Context Summarization

This middleware is designed to wrap the node where the agent actually call the LLM, to automatically summarize conversation history when approaching token limits. In Neuron there are three possible node in charge of this task based on the type of call you want to perform: `ChatNode`, `StreamingNode`, and `StructuredNode`. You should attach the middleware to all these nodes to be sure it works regardless of the mode the agent is running in.

```php
use NeuronAI\Agent\Agent;
use NeuronAI\Agent\Middleware\Summarization;
use NeuronAI\Agent\Nodes\ChatNode;
use NeuronAI\Agent\Nodes\StreamingNode;
use NeuronAI\Agent\Nodes\StructuredOutputNode;

class MyAgent extends Agent
{
    ...

    /**
     * Attach middleware to nodes.
     */
    protected function middleware(): array
    {
        $summarization = new Summarization(
            provider: $this->resolveProvider(), // Or use a dedicated provider instance
            maxTokens: 10000,
            messagesToKeep: 5,
        );
        
        return [
            ChatNode::class => [$summarization],
            StreamingNode::class => [$summarization],
            StructuredOutputNode::class => [$summarization]
        ];
    }
}
```

`maxTokens` and `messagesToKeep` work together to define the threshold beyond which the summary must be performed. In the example above, if the context reach 30K tokens, there must be at least 10 messages in the chat history for the summary to start. Adding new messages to the chat history will eventually cross both thresholds triggering the summarization.

### Tool Search

By default every time the provider is invoked all tools are loaded and transmitted to the backend LLM. A complex production agent connected to email, calendar, drive, CRM, and and multiple MCP servers can easily reach hundreds of tools, each carrying its name, description, parameter schema, and usage hints.

This burns thousands of tokens on every turn, but the more painful issue is quality: when a model sees too many tools at once, descriptions blur together, similar-sounding tools compete for attention, and the agent starts making subtly wrong choices, mixing up parameters or hallucinating arguments because it is trying to keep too many signatures in working memory at the same time.

Tool search reframes the tool catalog as something the agent queries on demand rather than something it carries on every request.

You can use the global middleware `ToolSearchMiddleware` to activate dynamic tool selection on your agent:

```php
class MyAgent extends Agennt
{
    ...
    
    /**
     * Define the global middleware.
     */
    protected function globalMiddleware(): array
    {
        return [
            new ToolSearchMiddleware([
                MyCustomTool::make(),
                ...CalculatorToolkit::make()->tools()
                ...MCPConnector::make([...])->tools()
            ]),
        ];
    }
    
    /**
     * Provide core tools to the agent.
     */
    protected function tools(): array
    {
        return [
            // A list of core tools that the model always has available
            TavilySearchTool::make(...),
        ];
    }
}
```

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

The middleware automatically injects the `ToolSearch` tool in the default tool list available to the model on every request, and keep the list of tools you provide in an internal array.

The agent starts a turn with a minimal tool set, usually just `ToolSearch` itself plus whatever core tools you always want available, and when it needs a capability it does not currently have, it calls `ToolSearch` with a natural language query that returns a ranked list of tool descriptors with their full schemas. At this point a middleware sitting between the agent and the next inference call inspects the search result, extracts the tool identifiers, looks them up in the underlying registry, and adds their full definitions to the tools array that will be sent on the next request to the model.

From the model's perspective the next turn simply arrives with a richer tool list, and it can invoke any of those newly surfaced tools directly with proper schema validation, exactly as if they had been there from the start.


# Async

Run workflows in an async context.

Neuron supports asynchronous execution and parallel processing of agents, enabling you to efficiently handle multiple operations simultaneously. Neuron's approach to asynchronous execution offers several advantages:

**Framework Agnostic**: You can make agents and RAG async friendly for the most common async environments with a simple adapter, no changes are required to your implementation.

**Providers Compatibility**: Our solution works regardless of the provider you use. You can have workflow or multi-agent systems using different providers and models running smoothly in an async loop.

**Scalability**: Applications handling large volumes of data (products classification, content moderation, data labeling) benefit significantly from concurrent processing capabilities.

### Concurrency vs Async

Concurrency is the high-level concept of managing tasks, which can involve multiple threads/cores (parallelism), whereas async uses event loops/callbacks to let tasks run out of order, perfect for I/O-bound work without waiting.

Concurrency is about managing many things in parallel, while asynchrony is how a single thread can manage many I/O operations efficiently.

AI Agents are typically considered I/O heavy software because the HTTP request to run inference on the model usually takes seconds to complete. In a standard PHP enviornment during this time your application just wait. In this section of the documentation we provide you with a couple of solutions to run multiple agent efficiently.

## Concurrency

You don't need any particular feature from Neuron to run multiple agents in parallel. You only need your PHP application to be able to span multiple processes to handle the execution of multiple agents at the same time. You can do this with PHP libraries like [spatie/fork](https://github.com/spatie/fork), or framework specific solutions like [Laravel concurrency](https://laravel.com/docs/master/concurrency), or [Symfony process](https://symfony.com/doc/current/components/process.html).

Async is a different story.

## Async

Previous versions of Neuron were strongly coupled with the Guzzle client to perform HTTP requests for model inference on the providers API. Guzzle is a great tool, but it's not compatible with truly async event loops like those provided by frameworks like [Amp](https://github.com/amphp/amp) and [ReactPHP](https://github.com/reactphp/reactphp).

In order to run agents in such async environments it's required to integrate with their specific implementations. That's why Neuron ships with a simple `HttpClientInterface` that can be implemented to allow AI providers run HTTP requests smoothly in an async loop.

By default the framework uses the Guzzle implementation, but you can inject custom HTTP clients based on your needs. We already provide implementations for the most common async framework.

### AmpHttpClient

If you want to use Amp to run multiple async agent requests you need to install `amphp/http-client` .

```bash
composer require amphp/http-client
```

Now you can inject the built-in `AmpHttpClient` adapter into the provider:

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\HttpClient\AmpHttpClient;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // It's the same for any provider (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
            httpClient: new AmpHttpClient(),
        );
    }
}
```

Now you can run multiple agent requests using Amp async/await pattern to run multiple agent asynchronously:

```php
use Amp\Future;

use function Amp\async;

$handler1 = MyAgent::make()->chat(new UserMessage('Hi!'));
$handler2 = MyAgent::make()->chat(new UserMessage('Hi!'));
$handler3 = MyAgent::make()->chat(new UserMessage('Hi!'));

// Run three requests in parallel
[$response1, $response2, $response3] = Future\await([
    async(fn() => $handler1->getMessage()), 
    async(fn() => $handler2->getMessage()),
    async(fn() => $handler3->getMessage()),
]);

// Print the content
echo $response1->getContent();
echo $response2->getContent();
echo $response3->getContent();
```

### Async RAG

The HTTP client abstraction is also accepted by all the other framework components, like embedding providers and vector stores. You can provide an async client to all this components and also run data loading pipeline asynchronously.

```php
class MyChatBot extends RAG
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
            httpClient: new AmpHttpClient(),
        );
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new VoyageEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL'
            httpClient: new AmpHttpClient(),
        );
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new PineconeVectorStore(
            key: 'PINECONE_API_KEY',
            indexUrl: 'PINECONE_INDEX_URL',
            httpClient: new AmpHttpClient(),
        );
    }
}
```

### Guardrails

The ability to execute agents asynchonously opens the possibility of running an input guardrail at the same time as the main request. Typically you can create a specialized agent to run security checks against the user input, and use the structured reponse to retrieve the result.

You can run both requests in parallel, and check the guardrail result before returning the response to the user. Running both requests in parallel allows you to enforce security without impacting the user experience.

```php
use Amp\Future;
use function Amp\async;

$input = new UserMessage('Hi!');

// Run three requests in parallel
[$response, $guardrail] = Future\await([
    async(fn() => MyAgent::make()->chat($input)->getMessage()), 
    async(fn() => GuardrailAgent::make()->structured($input, Guardrail::class)),
]);

if (! $guardrail->valid) {
    throw \Exception('Content policy violation.');
}

// Print the content
echo $response->getContent();
```


# Monitoring & Debugging

Monitor your AI Agents, RAGs, and Workflows in real-time.

The [Inspector](https://inspector.dev/) team designed Neuron with built-in observability features, so you can monitor AI agents running, helping you maintain production-grade implementations with confidence.

## Install Inspector

You can follow this step-by-step guide to connect your Neuron AI Agents, RAG, or Workflow to the Inspector monitoring dashboard:

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

After connecting you app to Inspector when your agents are being executed, you will be able to explore the details of their inference steps, tool calls, and more.

<figure><img src="/files/Bb5QhlnIr1o5ELFDIbwO" alt=""><figcaption></figcaption></figure>

If you want to monitor the whole application you can install the Inspector package based on your development environment. We provide integration packages for [PHP](https://github.com/inspector-apm/inspector-php), [Laravel](https://github.com/inspector-apm/inspector-laravel), [Symfony](https://github.com/inspector-apm/inspector-symfony), [CodeIgniter](https://github.com/inspector-apm/inspector-codeigniter), [Drupal](https://docs.inspector.dev/guides/drupal). Check out them on our [GitHub organization](https://github.com/inspector-apm).

### Create An Ingestion Key

To create an Ingestion key head to the [**Inspector dashboard**](https://app.inspector.dev/register) and create a new app.

{% hint style="success" %}
For any additional support drop in a live chat in the dashboard. We are happy to listen from your experience, find new possible improvements, and make the tool better overtime.
{% endhint %}

### Inject InspectorObeserver

If your application doesn't have a specific integration with PHP environment variables, you can inject the InspectorObserver component into the agent programmatically, passing the ingestion key generated in the dashboard:

```php
use Inspector\Neuron\InspctorObserver;

/*
 * Inject at runtime
 */
$message = MyAgent::make()
    ->observe(InspctorObserver::instance('INSPECTOR_INGESTION_KEY'))
    ->chat(...)
    ->getMessage();
    
/*
 * Setup the observer once into the agent constructor
 */
class MyAgent extends Agent
{
    public function __construct()
    {
        $this->observe(InspctorObserver::instance('INSPECTOR_INGESTION_KEY'));
    }
}
```

## Logging

If you want to report agent activity into your log system you can attach the built-in `LogObserver` to your agent passing an instance of a PSR `LoggerInterface` compatible logger, like monolog for example:

```php
use NeuronAI\Observability\LogObserver;

$message = MyAgent::make()
    ->observe(new LogObserver($logger))
    ->chat(...)
    ->getMessage();
```

All itnernal events with their payload will be logged.


# Error Handling

Managing errors fired by your agent.

All exceptions fired from Neuron AI are an extension of `NeuronException` . There are several types of exceptions that can help you understand what's going wrong, but because they inherit from the same root exception, they give you the ability to accurately detect agent errors in the context of your code:

```php
try {

    // Your code here...

} catch (NeuronAI\Exceptions\NeuronException $e) {
    // ...
} catch (NeuronAI\Exceptions\ProviderException $e) {
    // ...
} catch (NeuronAI\Exceptions\AgentException $e) {
    // ...
} catch (NeuronAI\Exceptions\ChatHistoryException $e) {
    // ...
} catch (NeuronAI\Exceptions\HttpException $e) {
    // ...
} catch (NeuronAI\Exceptions\ToolException $e) {
    // ...
} catch (NeuronAI\Exceptions\VectorStoreException $e) {
    // ...
} catch (NeuronAI\Exceptions\WorkflowException $e) {
    // ...
} catch (NeuronAI\Exceptions\DataReaderException $e) {
    // ...
}
```

### Monitoring & Debugging

If you want to be alerted on any error, consider to connect [**Inspector**](https://inspector.dev/) to your application.

After you sign up at the link above, make sure to set the `INSPECTOR_INGESTION_KEY` variable in the application environment file.

{% code title=".env" %}

```
INSPECTOR_INGESTION_KEY=nwse877auxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

{% endcode %}

The Agent will automatically instrument itself. Learn more on the [documentation](/agent/observability) for other configuration options.

<figure><img src="/files/AndP4MqLRPT1nj3HyJz7" alt=""><figcaption></figcaption></figure>


# Evals

Evaluating the output of your agentic system

This guide covers approaches to evaluating agents. Effective evaluation is essential for measuring agent performance, tracking improvements, and ensuring your agentic system meet quality standards.

When building AI applications, evaluating the consistency of their output is crucial, not only for the maintenance of the agent, but also to evaluate different architectures or prompting approach on the initial design phase.

It's important to consider various qualitative and quantitative factors, including response syntax, task completion, success, and inaccuracies or hallucinations. In evaluations, it's also important to consider comparing different configurations to optimize for specific desired outcomes. Given the dynamic and non-deterministic nature of LLMs, it's also important to have rigorous and frequent evaluations to ensure a consistent baseline for tracking improvements or regressions.

### Configuring your application

Like unit tests, it could be better to collect evaluators for your AI system into a dedicated directory. So, you can add the configuration below to your application `composer.json` file in order to tell composer how to include your evaluators in the application namespaces:

```json
"autoload-dev": {
    "psr-4": {
        ...,
        "App\\Evaluators\\": "evaluators/"
    }
},
```

Next create the `evaluators` directory in your project root folder. Keeping evaluation code separate from production code creates a clear boundary between what gets deployed to production and what exists purely for development and quality assurance.

### Creating Evaluators

Use the command below to create the `AgentEvaluator` class into the evaluators folder:

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

```bash
vendor/bin/neuron make:evaluator App\\Neuron\\Evaluators\\AgentEvaluator
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:evaluators App\Neuron\Evaluators\AgentEvaluator
```

{% endtab %}
{% endtabs %}

The class being created will have the following structure:

```php
namespace App\Neuron\Evaluators;

use NeuronAI\Evaluation\Assertions\StringContains;
use NeuronAI\Evaluation\BaseEvaluator;
use NeuronAI\Evaluation\Contracts\DatasetInterface;
use NeuronAI\Evaluation\Dataset\JsonDataset;

class AgentEvaluator extends BaseEvaluator
{
    /**
     * 1. Get the dataset to evaluate against
     */
    public function getDataset(): DatasetInterface
    {
        return new JsonDataset(__DIR__ . '/datasets/dataset.json');
    }

    /**
     * 2. Run the agent logic being tested
     */
    public function run(array $datasetItem): mixed
    {
        $response = MyAgent::make()->chat(
            new UserMessage($datasetItem['input'])
        )->getMessage();
        
        return $response->getContent();
    }

    /**
     * 3. Evaluate the output against expected results, with assertions
     */
    public function evaluate(mixed $output, array $datasetItem): void
    {
        $this->assert(
            new StringContains($datasetItem['reference']),
            $output,
        );
    }
} 
```

The logic is quite straightforward. The evaluator first load the dataset, and then run the evaluation for each item of the dataset.

In the `run` method you can execute your agentic entities with the example input and return the output. The output is then passed to the `evaluate` method where you can performs assetions comparing the output with a reference value or any other logic you want.

### Dataset Loader

You can use anything you want as dataset. There are no predefined format. The evaluator class simply allows you to load a list of test cases and run the evaluators against them. You have two dataset loaders.

#### ArrayDataset

```php
class AgentEvaluator extends BaseEvaluator
{
    public function getDataset(): DatasetInterface
    {
        return new ArrayDataset([
            [
                'input' => 'Hi',
                'reference' => 'help'
            ]
        ]);
    }
    
    ...
}
```

#### JsonDataset

```php
class AgentEvaluator extends BaseEvaluator
{
    public function getDataset(): DatasetInterface
    {
        return new JsonDataset(__DIR__ . '/datasets/dataset.json');
    }
    
    ...
}
```

You can eventually create a custom dataset loader implementing `NeuronAI\Evaluation\Contracts\DatasetInterface`.

### Running Evaluations

If you have properly configured your composer file you can use the Neuron CLI to launch the evaluators:

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

```bash
vendor/bin/neuron evaluations --path=evaluators
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron evaluations --path=evaluators
```

{% endtab %}
{% endtabs %}

### Assertions

We provide a set of built-in assertion for the most common use case. You can also implement your own assertion to design custom scoring systems. Check the next section.

**StringContains**

```php
$this->assert(new StringContains('positive'), $output);
```

**StringContainsAll**

Check if the output contains all keywords:

```php
$this->assert(new StringContainsAll(['hello', 'world']), $output);
```

**StringContainsAny**

Check if the output contains any of the keywords:

```php
$this->assert(new StringContainsAny(['success', 'completed']), $output);
```

**StringStartsWith**

Check if the output starts with a prefix:

```php
$this->assert(new StringStartsWith('Hello'), $output);
```

**StringEndsWith**

Check if the output ends with a suffix:

```php
$this->assert(new StringEndsWith('!'), $output);
```

**StringLengthBetween**

Check if the string length is within range:

```php
$this->assert(new StringLengthBetween(10, 100), $output);
```

**StringDistance**

Check string similarity using Levenshtein distance:

```php
$this->assert(new StringDistance(
    reference: 'expected text',
    threshold: 0.5, // Minimum similarity score
    maxDistance: 50 // Maximum allowed edits
), $output);
```

**StringSimilarity**

Check string similarity using embeddings:

```php
use NeuronAI\Evaluation\Assertions\StringSimilarity;
use NeuronAI\RAG\Embeddings\OpenAI\OpenAIEmbeddings;

$this->assert(new StringSimilarity(
    reference: 'The quick brown fox',
    embeddingsProvider: new OpenAIEmbeddings(key: 'YOUR_KEY'),
    threshold: 0.6
), $output);
```

**MatchesRegex**

Match against regular expression:

```php
$this->assert(new MatchesRegex('/^\d{3}-\d{2}-\d{4}$/'), $output);
```

**IsValidJson**

Check if the output is valid JSON:

```php
$this->assert(new IsValidJson(), $output);
```

### AI as a Judge

Use an AI agent to evaluate outputs with custom criteria. Neuron provdes you with the primitive class AgentJudge to define your custom creteria, otherwise you can use one of the built-in judge assertions.

```php
use NeuronAI\Evaluation\Assertions\AgentJudge;

class AgentJudgeEvaluator extends BaseEvaluator
{
    protected AgentInterface $judge;

    public function setUp(): void
    {
        $this->judge = Agent::make()
            ->setAiProvider(
                new Antrhopic(...)
            )
            ->setInstructions('You are an expert evaluator for customer support responses.');
    }

    public function getDataset(): DatasetInterface
    {
        return new JsonDataset(...);
    }

    public function run(array $datasetItem): mixed
    {
        $response = MyAgent::make()->chat(
            new UserMessage($datasetItem['input'])
        )->getMessage();
        
        return $response->getContent();
    }
    
    public function evaluate(mixed $output, array $datasetItem): void
    {
        $this->assert(new AgentJudge(
            judge: $this->judge,
            criteria: 'Response should be helpful, polite, and address the customer\'s question directly',
            threshold: $datasetItem['threshold']
        ), $output);
    }
}
```

#### Faithfulness Judge

Check if output is grounded in context (no hallucinations):

```php
$this->assert(new FaithfulnessJudge(
    judge: $this->judge,
    context: $retrievedDocuments,
    threshold: 0.7
), $output);
```

#### Correctness Judge

Compare to expected answer:

```php
$this->assert(new CorrectnessJudge(
    judge: $judge,
    expected: $datasetItem['expected_answer'],
    threshold: 0.7
), $output);
```

#### Relevance Judge

Check if output addresses the question:

```php
$this->assert(new RelevanceJudge(
    judge: $judge,
    question: $datasetItem['question'],
    threshold: 0.7
), $output);
```

#### Helpfulness Judge

Evaluate utility and actionability:

```php
$this->assert(new HelpfulnessJudge(
    judge: $judge,
    threshold: 0.7
), $output);
```

### Creating Custom Assertions

```php
use NeuronAI\Evaluation\Assertions\AbstractAssertion;
use NeuronAI\Evaluation\AssertionResult;

class GreaterThanAssertion extends AbstractAssertion
{
    public function __construct(
        private readonly float $threshold
    ) {}

    public function evaluate(mixed $actual): AssertionResult
    {
        if (!is_numeric($actual)) {
            return AssertionResult::fail(
                0.0,
                'Expected numeric value, got ' . gettype($actual),
            );
        }

        if ($actual > $this->threshold) {
            return AssertionResult::pass(1.0);
        }

        return AssertionResult::fail(
            0.0,
            "Expected {$actual} to be greater than {$this->threshold}",
        );
    }
}
```

### Output

The evaluation module uses a PHP configuration file to control how evaluation results are displayed. The config system supports multiple output drivers, enabling results to be sent to console, files, databases, or external APIs simultaneously.

#### **Config File**

Create the `evaluation.php` file in your project root:

```php
<?php

use NeuronAI\Evaluation\OutputDrivers\ConsoleDriver;
use NeuronAI\Evaluation\OutputDrivers\JsonDriver;

return [
    'output' => [
        // Output results in the console
        ConsoleDriver::class => ['verbose' => true],

        // Save results in a json file
        JsonDriver::class => ['path' => 'evaluation-results.json'],
    ],
];
```

You can declare an array of options for each output class. This configurations will be passed as arguments to the constructor of the output class implementation.

**If no config file exists**, the system defaults to `ConsoleOutputDriver` with standard output.

#### Creating Custom Output

Implement `EvaluationOutputInterface` to create custom output drivers:

```php
namespace App\Neuron\Evaluations;

use NeuronAI\Evaluation\Contracts\EvaluationOutputInterface;
use NeuronAI\Evaluation\Runner\EvaluatorSummary;

class DatabaseOutput implements EvaluationOutputInterface
{
    public function __construct(
        private readonly \PDO $pdo,
        private readonly string $table = 'evaluations'
    ) {}

    public function output(EvaluatorSummary $summary): void
    {
        $stmt = $this->pdo->prepare(
            "INSERT INTO {$this->table} (passed, failed, success_rate, total_time, created_at, updated_at) VALUES (?, ?, ?, ?, NOW(), NOW())"
        );
        $stmt->execute([
            $summary->getPassedCount(),
            $summary->getFailedCount(),
            $summary->getSuccessRate(),
            $summary->getTotalExecutionTime(),
        ]);
    }
}
```

Once you have created your output class you can register it in the configuration file, to be used the next time you run the evaluations.

```php
<?php

use NeuronAI\Evaluation\OutputDrivers\ConsoleDriver;
use NeuronAI\Evaluation\OutputDrivers\JsonDriver;

return [
    'output' => [
        // Output results in the console
        ConsoleDriver::class => ['verbose' => true],

        // Save results in a json file
        //JsonDriver::class => ['path' => 'evaluation-results.json'],
        
        // Save results in the database
        DatabaseOutput::class => [
            'pdo' => new \PDO(...),
            'table' => 'evaluations',
        ]
    ],
];
```

### Parallel Execution

By default the evaluation command processes dataset items one at a time. Since most evaluators spend their time waiting on AI provider responses, you can drastically reduce the total run time by processing multiple dataset items in parallel with the `--concurrency` option:

```bash
vendor/bin/neuron evaluation path/to/evaluators --concurrency=3
```

With `--concurrency=3`, up to 3 dataset items are evaluated at the same time, each in its own PHP child process. An evaluation that makes one 2-second LLM call per item over a 100-item dataset drops from \~200 seconds to \~66 seconds.

#### Requirements

Parallel execution relies on process forking, which requires:

* The [pcntl](https://www.php.net/manual/en/book.pcntl.php) PHP extension (available on Linux and macOS — not on Windows)
* The [spatie/fork](https://github.com/spatie/fork) package:

```bash
composer require --dev spatie/fork
```

If either is missing, the command prints a notice and automatically falls back to sequential execution, so the same command works in every environment.

#### Choosing a concurrency level

Every item in flight is an active request against your AI provider. Start with a moderate value (3–5) and increase it as long as you don't hit provider rate limits. If you see rate limit errors appearing as test failures, lower the value.

#### How it works, and what to watch out for

Each dataset item runs in a forked copy of your evaluator, and its result is sent back to the parent process. This has a few practical implications:

* **Results are unaffected.** Items are evaluated independently, results keep their dataset order, and the final report is identical to a sequential run.
* **State is not shared between items.** Each item sees the evaluator state as it was after `setUp()`. Side effects performed while handling one item (incrementing a property, appending to a file) are not visible to other items. If your evaluator relies on accumulating state across items, keep running it sequentially.
* **Outputs must be serializable.** The value returned by `run()` crosses a process boundary via `serialize()`. If it can't be serialized (e.g. it contains a closure or an open connection), the assertion results are preserved but the output shown in reports is replaced with a placeholder string.

#### Execution time reporting

The reported total time is the real wall-clock duration of the run, while the average time per test reflects the actual duration of each individual item — so under parallel execution the average per test can be larger than the total divided by the number of tests.

### Custom bootstrap file

By default, the `neuron` CLI only loads your project's Composer autoloader (`vendor/autoload.php`). That's enough when your classes are plain PHP resolvable by Composer, but often they aren't: an evaluator might read config through your framework's helpers, need environment variables loaded from `.env`, rely on constants, or require a service container to be initialized. In those cases the command would fail with "class not found" or missing-configuration errors, because the code that normally prepares that environment — your framework's bootstrap — never runs.

The `--autoload-file` option solves this by letting you point the CLI to a PHP file to execute *before* the command starts, in addition to the default Composer autoloader:

```bash
vendor/bin/neuron evaluation /path/to/evaluators --autoload-file=bootstrap.php
```

The file can do anything a normal bootstrap does — register additional autoloaders, load environment variables, define constants, or boot your framework. For example, to run evaluators that depend on a Laravel application:

```php
<?php
// bootstrap.php

require __DIR__.'/vendor/autoload.php';

$app = require_once __DIR__.'/bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
```


# Testing

Fake components to help you test your AI powered system

When you test an agent, you don't want every test run to make real API calls to OpenAI, Anthropic, or any other provider. Real calls are slow, cost money, and return different results every time, making your tests flaky and expensive. The same applies to RAG agents: you don't want to spin up a vector database or call an embeddings API just to verify your agent's logic.

Neuron ships with drop-in test doubles that solve this problem. `FakeAIProvider` replaces the AI provider, `FakeEmbeddingsProvider` replaces the embeddings provider, and `FakeVectorStore` replaces the vector store. They return predetermined responses, never hit the network, and record every interaction so you can assert exactly what your agent did.

### Setup

Create a `FakeAIProvider` with the responses you expect the model to return, then inject it into your agent:

```php
use NeuronAI\Chat\Messages\Stream\AssistantMessage;
use NeuronAI\Testing\FakeAIProvider;

$provider = new FakeAIProvider(
    new AssistantMessage('Hello! How can I help you?')
);

$agent = MyAgent::make()->setAiProvider($provider);
```

Responses are returned in order. If your agent makes multiple calls to the provider (e.g. tool calls), queue multiple responses:

```php
$provider = new FakeAIProvider(
    new AssistantMessage('First response'),
    new AssistantMessage('Second response'),
);
```

### Chat

```php
public function test_agent_responds(): void
{
    $provider = new FakeAIProvider(
        new AssistantMessage('The capital of France is Paris.')
    );

    $agent = MyAgent::make()->setAiProvider($provider);

    $message = $agent->chat(new UserMessage('What is the capital of France?'))->getMessage();

    $this->assertSame('The capital of France is Paris.', $message->getContent());
    $provider->assertCallCount(1);
}
```

### Streaming

The fake provider splits the response text into chunks, simulating a real stream:

```php
public function test_agent_streams_response(): void
{
    $provider = new FakeAIProvider(
        new AssistantMessage('Hello world')
    );

    $agent = MyAgent::make()->setAiProvider($provider);

    $handler = $agent->stream(new UserMessage('Hi'));

    $chunks = [];
    foreach ($handler->events() as $event) {
        if ($event instanceof \NeuronAI\Chat\Messages\Stream\Chunks\TextChunk) {
            $chunks[] = $event->content;
        }
    }

    // The response is split into chunks of 5 characters by default
    $this->assertSame(['Hello', ' worl', 'd'], $chunks);

    // The final message is available after the stream is consumed
    $state = $handler->run();
    $this->assertSame('Hello world', $state->getMessage()->getContent());
}
```

You can change the chunk size with `setStreamChunkSize()`:

```php
$provider->setStreamChunkSize(10);
```

### Structured Output

Provide a JSON string that matches your output class schema. The agent will deserialize and validate it as usual:

```php
public function test_agent_returns_structured_output(): void
{
    $provider = new FakeAIProvider(
        new AssistantMessage('{"name": "Alice"}')
    );

    $agent = MyAgent::make()->setAiProvider($provider);

    $user = $agent->structured(new UserMessage('Generate a user'), User::class);

    $this->assertInstanceOf(User::class, $user);
    $this->assertSame('Alice', $user->name);
}
```

### Tool Calls

When the model decides to call a tool, it returns a `ToolCallMessage`. The agent executes the tool and loops back to the provider for a final answer. Queue both responses:

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

public function test_agent_uses_tools(): void
{
    $searchTool = Tool::make('search', 'Search the web')
        ->addProperty(new ToolProperty('query', PropertyType::STRING, 'Search query', true))
        ->setCallable(fn (string $query): string => "Results for: {$query}");

    $provider = new FakeAIProvider(
        // First call: the model asks to use the search tool
        new ToolCallMessage(null, [
            (clone $searchTool)->setCallId('call_1')->setInputs(['query' => 'PHP frameworks']),
        ]),
        // Second call: the model responds using the tool result
        new AssistantMessage('Here are the top PHP frameworks...')
    );

    $agent = MyAgent::make()
        ->setAiProvider($provider)
        ->addTool($searchTool);

    $message = $agent->chat(new UserMessage('Best PHP frameworks?'))->getMessage();

    $this->assertSame('Here are the top PHP frameworks...', $message->getContent());
    $provider->assertCallCount(2);
}
```

### Assertions

`FakeAIProvider` includes built-in assertions you can use in your tests:

```php
// Verify the total number of provider calls
$provider->assertCallCount(2);

// Verify calls by method
$provider->assertMethodCallCount('chat', 1);
$provider->assertMethodCallCount('stream', 1);

// Verify no calls were made
$provider->assertNothingSent();

// Verify the system prompt
$provider->assertSystemPrompt('You are a helpful assistant.');

// Verify tools were configured
$provider->assertToolsConfigured(['search', 'calculator']);

// Custom assertion with a callback
$provider->assertSent(fn (RequestRecord $record): bool =>
    $record->method === 'chat'
    && $record->messages[0]->getContent() === 'Hello'
);
```

### Inspecting Requests

For more advanced checks, access the raw recorded requests:

```php
$records = $provider->getRecorded();

$records[0]->method;          // 'chat', 'stream', or 'structured'
$records[0]->messages;        // Message[] sent to the provider
$records[0]->systemPrompt;    // The system prompt at call time
$records[0]->tools;           // The tools configured at call time
$records[0]->structuredClass; // The output class (structured calls only)
$records[0]->structuredSchema; // The JSON schema (structured calls only)
```

## RAG

RAG agents depend on an embeddings provider and a vector store in addition to the AI provider. Neuron provides `FakeEmbeddingsProvider` and `FakeVectorStore` to replace both in tests.

#### FakeEmbeddingsProvider

Generates deterministic embeddings without calling any external API. Drop it in wherever you need an embeddings provider:

```php
use NeuronAI\Testing\FakeEmbeddingsProvider;

$embeddings = new FakeEmbeddingsProvider();
```

#### FakeVectorStore

Returns predetermined documents from `similaritySearch()` regardless of the embedding passed in. Pass the documents you want returned to the constructor:

```php
use NeuronAI\RAG\Document;
use NeuronAI\Testing\FakeVectorStore;

$vectorStore = new FakeVectorStore([
    new Document('Paris is the capital of France.'),
    new Document('Berlin is the capital of Germany.'),
]);
```

#### RAG Chat

```php
public function test_rag_answers_from_documents(): void
{
    $provider = new FakeAIProvider(
        new AssistantMessage('Paris is the capital of France.')
    );

    $vectorStore = new FakeVectorStore([
        new Document('France is a country in Europe. Its capital is Paris.'),
    ]);

    $rag = MyRAG::make()
        ->setAiProvider($provider);
        ->setEmbeddingsProvider(new FakeEmbeddingsProvider());
        ->setVectorStore($vectorStore);

    $message = $rag->chat(new UserMessage('What is the capital of France?'))->getMessage();

    $this->assertSame('Paris is the capital of France.', $message->getContent());
    $provider->assertCallCount(1);
    $vectorStore->assertSearchCount(1);
}
```

#### Adding Documents

Test that your indexing pipeline correctly embeds and stores documents:

```php
public function test_documents_are_embedded_and_stored(): void
{
    $embeddings = new FakeEmbeddingsProvider();
    $vectorStore = new FakeVectorStore();

    $rag = MyRAG::make()
        ->setAiProvider(new FakeAIProvider());
        ->setEmbeddingsProvider($embeddings);
        ->setVectorStore($vectorStore);

    $rag->addDocuments([
        new Document('First document'),
        new Document('Second document'),
    ]);

    $embeddings->assertCallCount(2);
    $vectorStore->assertDocumentCount(2);
    $vectorStore->assertHasDocumentWithContent('First document');
}
```

#### RAG Assertions

```php
// FakeEmbeddingsProvider
$embeddings->assertCallCount(2);
$embeddings->assertEmbeddedText('Some specific text');
$embeddings->assertNothingEmbedded();

// FakeVectorStore
$vectorStore->assertSearchCount(1);
$vectorStore->assertDocumentCount(3);
$vectorStore->assertHasDocumentWithContent('Expected content');
$vectorStore->assertNothingStored();
```


# AI Provider

Interact with LLM providers or extend the framework to implement new ones.

With Neuron you can switch between LLM providers with just one line of code, without any impact on your agent implementation.

### Anthropic

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

#### Anthropic Prompt Cache

Anthropic provider expose a dedicated method `systemPromptBlocks()` to leverage system prompt cache. Instead of using the `instructions()` method in the Agent class, you can pass prompts definition directly to the provider instance with cache type definition.

```php
class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_KEY',
            model: 'ANTHROPIC_MODEL'
        )->systemPromptBlocks([
            ['type' => 'text', 'text' => 'Static instructions...', 'cache_control' => ['type' => 'ephemeral']],
            ['type' => 'text', 'text' => 'Dynamic context...']
        ]);
    }
}
```

### Anthropic On Google Vertex AI

To use this provider you need to install the goole auth composer package:

```shellscript
composer require google/auth
```

Below the syntax to use `AnthropicVertex` in your agent.

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\AnthropicVertex;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new AnthropicVertex(
            pathJsonCredentials: 'GOOGLE_FILE_CREDENTIALS_PATH',
            location: 'GOOGLE_LOCATION',
            projectId: 'GOOGLE_PROJECT_ID',
            model: 'ANTHROPIC_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
        );
    }
}
```

### OpenAIResponses

This component uses the most recent OpenAI responses API:

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\Responses\OpenAIResponses;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAIResponses(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### OpenAI

This component uses the old OpenAI completions API:

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\OpenAI;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAI(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### AzureOpenAI

This provider allows you to connect with OpenAI models provided in the Azure cloud platform.

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\AzureOpenAI;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new AzureOpenAI(
            key: 'AZURE_API_KEY',
            endpoint: 'AZURE_ENDPOINT',
            model: 'OPENAI_MODEL',
            version: 'AZURE_API_VERSION'
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### OpenAILike

This class simplify the connection with providers offering the same data format of the official OpenAI API.

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAILike;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAILike(
            baseUri: 'https://api.together.xyz/v1',
            key: 'API_KEY',
            model: 'MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### Ollama

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Ollama\Ollama;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### Gemini

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Gemini;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Gemini(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### Gemini on Vertex AI

To use this provider you need to install the goole auth composer package:

```bash
composer require google/auth
```

Below the syntax to use `GeminiVertex` in your agent.

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\GeminiVertex;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new GeminiVertex(
            pathJsonCredentials: 'GOOGLE_FILE_CREDENTIALS_PATH',
            location: 'GOOGLE_LOCATION',
            projectId: 'GOOGLE_PROJECT_ID',
            model: 'GEMINI_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
        );
    }
}
```

### Mistral

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Mistral\Mistral;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Mistral(
            key: 'MISTRAL_API_KEY',
            model: 'MISTRAL_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### ZAI

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\ZAI\ZAI;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new ZAI(
            key: 'ZAI_API_KEY',
            model: 'glm-5',
            parameters: [], // Add custom params (temperature, logprobs, etc)
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### HuggingFace

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HuggingFace\HuggingFace;
use NeuronAI\Providers\HuggingFace\InferenceProvider;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new HuggingFace(
            key: 'HF_ACCESS_TOKEN',
            model: 'mistralai/Mistral-7B-Instruct-v0.3',
            // https://huggingface.co/docs/inference-providers/en/index
            inferenceProvider: InferenceProvider::HF_INFERENCE,
            parameters: [
                'max_tokens' => 500,
                'temperature' => 0.5
            ]
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### Deepseek

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Deepseek\Deepseek;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Deepseek(
            key: 'DEEPSEEK_API_KEY',
            model: 'DEEPSEEK_MODEL',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### Grok (X-AI)

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\XAI\Grok;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Grok(
            key: 'GROK_API_KEY',
            model: 'grok-4',
            parameters: [], // Add custom params (temperature, logprobs, etc)
            strict_response: false, // Strict structured output
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### AWS Bedrock Runtime

To use The `BedrockRuntime` provider you need to install the [`aws/aws-sdk-php`](https://github.com/aws/aws-sdk-php) package.

```bash
composer require aws/aws-sdk-php
```

Below you can find the syntax to use it in your agent.

```php
namespace App\Neuron;

use Aws\BedrockRuntime\BedrockRuntimeClient;
use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\AWS\BedrockRuntime;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        $client = new BedrockRuntimeClient([
            'version' => 'latest',
            'region' => 'us-east-1',
            'credentials' => [
                'key' => 'AWS_BEDROCK_KEY',
                'secret' => 'AWS_BEDROCK_SECRET',
            ],
        ]);
        
        return new BedrockRuntime(
            client: $client,
            model: 'AWS_BEDROCK_MODEL',
            inferenceConfig: []
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### Cohere

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Cohere\Cohere;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Cohere(
            key: 'COHERE_API_KEY',
            model: 'command-a-reasoning-08-2025',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

### Alibaba DashScope

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Alibaba\DashScopeOpenAI;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new DashScopeOpenAI(
            key: 'DS_API_KEY',
            model: 'wan2.6-t2i',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

## Routing

Official [Neuron Router](https://github.com/neuron-core/router) adds a reliability and management layer between the Agent session and providers API, giving you and your appliaction several key benefits.

#### Provider Failover for High Availability <a href="#provider-failover-for-high-availability" id="provider-failover-for-high-availability"></a>

Providers API occasionally experiences outages or rate limiting. Using the RouterProvider, your requests automatically fail over between multiple underlying providers. If one provider is unavailable or rate-limited, the router seamlessly routes to another, keeping your sessions uninterrupted.

#### Routing logic control

You can use routing logic like `RoundRobin` as a load balancer, `ContentRule` to route the request based on the content blocks inside the message (images, files, audio, video), or `DifficultyRule` to determine which model has the best capabilities to handle the incoming prompt.&#x20;

First install the package:

```shellscript
composer require neuron-core/router
```

Now use the `RouterProvider` class as any other provider in your agent class:

```php
use NeuronAI\Router\RouterProvider;
use NeuronAI\Router\Rules\MethodRule;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\Providers\OpenAI\OpenAI;

class MyAgent extens Agent
{
    protected function provider(): AIProviderInterface
    {
        return RouterProvider::make()
            ->addProvider('anthropic', new Anthropic(
                key: 'ANTHROPIC_API_KEY',
                model: 'claude-sonnet-4-20250514',
            ))
            ->addProvider('openai', new OpenAI(
                key: 'OPENAI_API_KEY',
                model: 'gpt-4o',
            ))
            ->setRule(
                new RoundRobinRule(['anthropic', 'openai'])
            );
    }

    protected function instructions(): string
    {...}

    protected function tools(): array
    {...}
}
```

In the example above we use the `RoundRobinRule` making the router act as a load balancer between the attached AI providers. The package ships with several built-in rules including an LLM classifier to route calls to the appropriate model based on the promp difficulty score: <https://github.com/neuron-core/router#difficultyrule>

## Custom Http Client

Providers use an HTTP client to communicate with the remote service. You can customize the configuration of the HTTP client explicitly passing an instance with custom constructor parameters, like timeout, custom headers, etc.

```php
use NeuronAI\Providers\HttpClientOptions;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
            httpClient: new GuzzleHttpClient(
                customHeaders: [...],
                timeout: 30,
            )
        );
    }
}
```

## Implement a custom provider

If you want to create a new provider you have to implement the `AIProviderInterface` interface:

```php
namespace NeuronAI\Providers;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\Tools\ToolInterface;
use NeuronAI\Providers\MessageMapperInterface;

interface AIProviderInterface
{
    /**
     * Send predefined instruction to the LLM.
     */
    public function systemPrompt(?string $prompt): AIProviderInterface;

    /**
     * Set the tools to be exposed to the LLM.
     *
     * @param array<ToolInterface> $tools
     */
    public function setTools(array $tools): AIProviderInterface;
    
    /**
     * The component responsible for mapping the NeuronAI Message to the AI provider format.
     */
    public function messageMapper(): MessageMapperInterface;

    /**
     * Send a prompt to the AI agent.
     */
    public function chat(array $messages): Message;
    
    /**
     * Yield the LLM response.
     */
    public function stream(array|string $messages, callable $executeToolsCallback): \Generator;
    
    /**
     * Schema validated response.
     */
    public function structured(string $class, Message|array $messages, int $maxRetry = 1): mixed;
}
```

The `chat` method should contains the call the underlying LLM. If the provider doesn't support tools and function calls, you can implement it with a placeholder.

This is the basic template for a new AI provider implementation.

```php
namespace App\Neuron\Providers;

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use NeuronAI\Chat\Messages\AssistantMessage;
use NeuronAI\Chat\Messages\Message;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\HandleWithTools;
use NeuronAI\Providers\MessageMapperInterface;

class MyAIProvider implements AIProviderInterface
{
    use HandleWithTools;
    
    /**
     * The http client.
     *
     * @var Client
     */
    protected Client $client;

    /**
     * System instructions.
     *
     * @var string
     */
    protected string $system;

    /**
     * The component responsible for mapping the NeuronAI Message to the AI provider format.
     *
     * @var MessageMapperInterface
     */
    protected MessageMapperInterface $messageMapper;
    
    public function __construct(
        protected string $key,
        protected string $model
    ) {
        $this->client = new Client([
            'base_uri' => 'https://api.provider.com/v1',
            'headers' => [
                'Content-Type' => 'application/json',
                'Authorization' => "Bearer {$this->key}",
            ]
        ]);
    }

    /**
     * @inerhitDoc
     */
    public function systemPrompt(string $prompt): AIProviderInterface
    {
        $this->system = $prompt;
        return $this;
    }

    public function messageMapper(): MessageMapperInterface
    {
        return $this->messageMapper ?? $this->messageMapper = new MessageMapper();
    }

    /**
     * @inerhitDoc
     */
    public function chat(array $messages): Message
    {
        $result = $this->client->post('chat', [
            RequestOptions::JSON => [
                'model' => $this->model,
                'messages' => \array_map(function (Message $message) {
                    return $message->jsonSerialize();
                }, $messages)
            ]
        ])->getBody()->getContents();
        
        $result = \json_decode($result, true);

        return new AssistantMessage($result['content']);
    }
}
```

After creating your own implementation you can use it in the agent:

```php
namespace App\Neuron;

use App\Neuron\Providers\MyAIProvider;
use NeuronAI\Agent;
use NeuronAI\Providers\AIProviderInterface;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new MyAIProvider (
            key: 'PROVIDER_API_KEY',
            model: 'PROVIDER_MODEL',
        );
    }
}
```

{% hint style="warning" %}
We strongly recommend you to submit new provider implementations via PR on the official repository or using other [Inspector.dev](https://inspector.dev/developer-support/) support channels. The new implementation can receives an important boost in its advancement by the community.
{% endhint %}


# Audio

Connect providers specialized in processing Audio to Text and vice-versa

Usually pure AI Audio services don't support full agentic abilities like tools and conversation. So, you can use these components as stanalone services in an agentic workflow, or use them inside an Agent since they implement the `AIProviderInterface` interface. In this case you can benefit from the agentic workflow features like middleware and guardrails.

These component can be helpful for creating local voice assistants for hands-free interaction with models. The typical flow involves capturing audio, transcribing it to text with a separate Speech-To-Text (STT) service, sending that text to an agent for processing, and then using Text-to-Speech (TTS) to speak the response.

### As an Agent provider

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\Audio\OpenAITextToSpeech;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAITextToSpeech(
            key: 'OPENAI_API_KEY',
            model: 'gpt-4o-mini-tts',
            voice: 'alloy',
        );
    }
}

// Run the agent
$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

// Retrieve the audio part of the message (it's in base64 format)
$audioBase64 = $message->getAudio()->getContent();

// Save the audio file
file_put_contents(__DIR__.'/assets/speech.mp3', base64_decode($audioBase64));
```

### Direct use

```php
$provider = new OpenAITextToSpeech(
    key: 'OPENAI_API_KEY',
    model: 'gpt-4o-mini-tts',
    voice: 'alloy',
);

// Generate speech from text
$message = $provider->chat(new UserMessage("Hi, I'm the creator of Neuron AI framework!"));

// Retrieve the audio part of the message (it's in base64 format)
$audioBase64 = $message->getAudio()->getContent();

// Save the audio file
file_put_contents(__DIR__.'/assets/speech.mp3', base64_decode($audioBase64));
```

## OpenAI Audio

### Text-To-Speech

```php
use NeuronAI\Providers\OpenAI\Audio\OpenAITextToSpeech;

$provider = new OpenAITextToSpeech(
    key: 'OPENAI_API_KEY',
    model: 'gpt-4o-mini-tts',
    voice: 'alloy',
);

// Generate speech from text
$message = $provider->chat(new UserMessage("Hi, I'm the creator of Neuron AI framework!"));

// Retrieve the audio part of the message (it's in base64 format)
$audioBase64 = $message->getAudio();

// Save the audio file
file_put_contents(__DIR__.'/assets/speech.mp3', base64_decode($audioBase64));
```

### Speech-To-Text

```php
use NeuronAI\Providers\OpenAI\Audio\OpenAISpeechToText;

$provider = new OpenAISpeechToText(
    key: 'OPENAI_API_KEY',
    model: 'gpt-4o-transcribe',
);

// Transcribe the audio
$message = $provider->chat(
    new UserMessage([
        new TextContent('This audio is about a math lesson. Take care of the technical words.'),
        new AudioContent(__DIR__ . '/assets/intro.mp3', SourceType::URL)
    ])
);

// Print the text gathered from the audio file
echo $message->getContent();
```

## ElevenLabs

### Text-To-Speech

```php
use NeuronAI\Providers\ElevenLabs\ElevenLabsTextToSpeech;

$provider = new ElevenLabsTextToSpeech(
    key: 'ELEVENLABS_API_KEY',
    model: 'eleven_multilingual_v2',
    voice: 'alloy',
);

// Generate speech from text
$message = $provider->chat(new UserMessage("Hi, I'm Valerio from Italy!"));

// Retrieve the audio part of the message (it's in base64 format)
$audioBase64 = $message->getAudio();

// Save the audio file
file_put_contents(__DIR__.'/asserts/speech.mp3', base64_decode($audioBase64));
```

### Speach-To-Text

```php
use NeuronAI\Providers\ElevenLabs\ElevenLabsSpeechToText;

$provider = new ElevenLabsSpeechToText(
    key: 'ELEVENLABS_API_KEY',
    model: 'scribe_v2',
);

// Transcribe the audio
$message = $provider->chat(
    new UserMessage(
        new AudioContent(__DIR__ . '/assets/intro.mp3', SourceType::URL)
    )
);

// Print the text gathered from the audio file
echo $message->getContent();
```

## ZAI

### Speech-To-Text

```php
use NeuronAI\Providers\ZAI\Audio\ZAITranscription;

$provider = new ZAITranscription(
    key: 'ZAI_API_KEY',
    model: 'glm-asr-2512',
);

// Transcribe the audio
$message = $provider->chat(
    new UserMessage(
        new AudioContent(__DIR__ . '/assets/intro.mp3', SourceType::URL)
    )
);

// Print the text gathered from the audio file
echo $message->getContent();
```


# Image

Generate images from text

Usually pure AI Audio services don't support full agentic abilities like tools and conversation. So, you can use these components as stanalone services in an agentic workflow, or use them inside an Agent since they implement the `AIProviderInterface` interface. In this case you can benefit from the agentic workflow features like middleware and guardrails.

These component can be helpful for automating image generation based on textual prompts.

## Nano Banana (Google)

Google Gemini API provides a full multimodality experience, so you can just change the default model in your Gemini provider to generate images from prompts. Neuron also supports iteration on generated images with multi-turn conversations thanks to its multimodal message layer and chat history management.

Just configure one of the image generation model in Google Gemini provider:

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Gemini;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Gemini(
            key: 'GEMINI_API_KEY',
            model: 'gemini-2.5-flash-image',
        );
    }
}

// Run the agent
$message = MyAgent::make()
    ->chat(new UserMessage("Generate an image of a venue hosting the best PHP conference!"))
    ->getMessage();

// Retrieve the image part of the message (it's in base64 format)
$imageBase64 = $message->getImage()->getContent();

// Save the image
file_put_contents(__DIR__.'/assets/cover.png', base64_decode($imageBase64));
```

## OpenAI Image

### As an Agent provider

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\Image\OpenAIImage;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new OpenAIImage(
            key: 'OPENAI_API_KEY',
            model: 'gpt-image-1.5',
        );
    }
}

// Run the agent
$message = MyAgent::make()
    ->chat(new UserMessage("Generate an image of a venue hosting the best PHP conference!"))
    ->getMessage();

// Retrieve the image part of the message (it's in base64 format)
$imageBase64 = $message->getImage()->getContent();

// Save the image
file_put_contents(__DIR__.'/assets/cover.png', base64_decode($imageBase64));
```

### Direct use

```php
use NeuronAI\Providers\OpenAI\Image\OpenAIImage;

$provider = new OpenAIImage(
    key: 'OPENAI_API_KEY',
    model: 'gpt-image-1.5',
);

// Generate speech from text
$message = $provider->chat(new UserMessage("Generate an image of a venue hosting the best PHP conference!"));

// Retrieve the image part of the message (it's in base64 format)
$imageBase64 = $message->getImage()->getContent();

// Save the image
file_put_contents(__DIR__.'/assets/cover.png', base64_decode($imageBase64));
```

## ZAI Image

### As an Agent Provider

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\ZAI\Image\ZAIImage;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new ZAIImage(
            key: 'ZAI_API_KEY',
            model: 'glm-image',
        );
    }
}

// Run the agent
$message = MyAgent::make()
    ->chat(new UserMessage("Generate an image of a venue hosting the best PHP conference!"))
    ->getMessage();

// Print the URL of the image
echo $message->getImage()->getContent();

```

### Direct Use

```php
use NeuronAI\Providers\ZAI\Image\ZAIImage;

$provider = new ZAIImage(
    key: 'ZAI_API_KEY',
    model: 'glm-image',
);

// Generate speech from text
$message = $provider->chat(new UserMessage("Generate an image of a venue hosting the best PHP conference!"));

// Print the URL of the image
echo $message->getImage()->getContent();
```


# Getting Started

Step by Step guide on how to implement Retrieval-Augmented Generation with Neuron framework.

{% hint style="info" %}

#### PREREQUISITES

This guide assumes you are already familiar with the following concepts:

* [Agent](/agent/agent)
* [Tool & Function Call](/agent/tools)
  {% endhint %}

Retrieval-Augmented Generation (RAG) is the process of providing references to a knowledge base outside of the LLM training data sources before generating a response.

Large Language Models (LLMs) are trained on vast volumes of data to be able to generate original output for tasks like answering questions, translating languages, and completing sentences. RAG extends the already powerful capabilities of LLMs to specific domains or an organization's internal knowledge base, all without the need to retrain the model.

It is a cost-effective approach to improving LLM output so it remains relevant, accurate, and useful also working on your own private data.

## Why RAG systems are relevant

Building a RAG system is the way to use the powerful LLM capabilities on your own private data. You can create applications capable of accurately answering questions about company internal documentations. Or chatbot to serve external customers on the internal rules of an organization.

If it's not about the usage of private data, you can think of RAG as a way to provide the latest research, statistics, or news to the generative models.

## How to create a RAG system

Without RAG, the LLM takes the user input and creates a response based on information it was trained on (what it already knows).

With RAG, an information retrieval component is introduced. It utilizes the user input to first pull information from a new data source. The user query and the relevant information retrieved are both given to the LLM. The LLM uses the new knowledge and its training data to create accurate responses. The following sections provide an overview of the process.

Even if it can appear a little bit complicated, don't worry, this is just to make you aware of the process. Most of these things are automatically managed by the Neuron RAG agent.

There are three most important steps to create a RAG system.

### Process external data

The external data you want to use to augment the default LLM knowledge may exist in various formats like files, database records, or long-form text.

Before being able to submit this data to the LLM you have to convert them into a specific format called "[Embeddings](https://inspector.dev/vector-store-ai-agents-beyond-the-traditional-data-storage/)".

### Retrieval

The embeddings you have generated by processing documents and data need to be stored in specific databases able to deal with this particlar format. These database are called "[Vector Store](https://inspector.dev/vector-store-ai-agents-beyond-the-traditional-data-storage/)".

Vector store are not only able to store this data, but also to perform a particular form of search: the "similarity search" between the existing data in the database an a query we provide.

### Augment the LLM prompt

Next, the RAG agent augments your input (or prompt) by adding the relevant retrieved data in the context to make the LLM aware of the custom data before generating the response.

You just need to take care of the first step "Process external data", and Neuron gives you the toolkit to make it simple. The other steps are automatically managed by the Neuron RAG agent.

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

## Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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

## Create a RAG Agent

To create a RAG you need to attach some additional components other than the AI provider, such as a `vector store`, and an `embeddings provider`.

First, let's create the RAG class:

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

```bash
vendor/bin/neuron make:rag App\\Neuron\\MyChatBot
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:rag App\Neuron\MyChatBot
```

{% endtab %}
{% endtabs %}

Here is an example of a RAG implementation:

```php
namespace App\Neuron;

use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAIEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL'
        );
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(
            directory: __DIR__,
            name: 'demo'
        );
    }
}
```

{% hint style="warning" %}
Explore [**Data Loaders**](/rag/data-loader) to learn how to populate the vector store with embeddings representing the knowledge you want to integrate as additional knowledge.
{% endhint %}

### Talk to the chat bot

Imagine having previously populated the vector store with the knowledge base you want to connect to the RAG agent, and now you want to ask questions. Check out [**Data Loaders**](/rag/data-loader) to learn about RAG data population.

To start the execution of a RAG you call the `chat()` method:

```php
use App\Neuron\MyChatBot;
use NeuronAI\Chat\Messages\UserMessage;

$message = MyChatBot::make()
    ->chat(
        new UserMessage('I want to know more about Inspector AI Bug Fix.')
    )
    ->getMessage();
    
echo $message->getContent();

// Sure, Inspector AI Bug Fix is an agentic monitoring tool 
// that provides bug fix proposals in real-time as an error occurs 
// in your application.
```

## Feed Your RAG With Documents

Once you have defined the components of your RAG system it's time to feed the vector database with embedded chunks of text.

Neuron provides you with [Data Loaders](/rag/data-loader) to help you set up a data loading pipeline with just a few lines of code. You can see an example below. To learn more about data loader you should check out the [dedicated documentation](/rag/data-loader):

```php
use App\Neuron\MyChatBot;
use NeuronAI\RAG\DataLoader\FileDataLoader;

MyChatBot::make()->addDocuments(
    // Use the file data loader component to load a text file into the vector store
    FileDataLoader::for(__DIR__.'/my-article.md')->getDocuments()
);
```

## RAG + Tools

The Neuron's RAG class extends the basic `\NeuronAI\Agent` class. This means that your RAG is always an agent and you can also attach tools and define system instructions in your implementation.

Imagine we want to implement an agent able to give workout tips based on the user data. Here is an example of a complete implementation:

```php
namespace App\Neuron;

use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;
use NeuronAI\Tools\Toolkits\Calculator\CalculatorToolkit;

class WorkoutTipsAgent extends RAG
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are an AI Agent specialized in providing workout tips."],
        );
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAIEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_MODEL'
        );
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(
            directory: __DIR__,
            name: 'demo'
        );
    }
    
    protected function tools(): array
    {
        return [
            CalculatorToolkit::make(),
        ];
    }
}
```

In the example above we created a RAG agent that is able to give workout tips to the user. We can load into the vector store the knowledge for the specific workouts you provide, so the agent has the knowledge to provide tips based on the current workout status of the user retrieved from the database with the tool we attached.

### RAG Workflow

In the following image you can see the complete representation of the workflow Neuron runs for a RAG application. Learning this structure can help you better understand the underlying execution process to hook the system using middleware:

<figure><img src="/files/AWyyTNUJUbr1Mewp4WI1" alt=""><figcaption></figcaption></figure>

Once the `UserMessage` comes into the system, it runs in order:

* `PreProcessQueryNode`: Run the [pre-processors](/rag/pre-post-processor#pre-processors) pipeline like `QueryTransformationPreProcessor` to reinforce the input prompt.
* `RetrieveDocumentsNode`: Execute the [retrieval strategy](/rag/retrieval) from the vector store or external data sources
* `PostProcessDocumentsNode`: Run the [post-processors](/rag/pre-post-processor#post-processors) pipeline like reranking
* `EnrichInstructionsNode`: Add the final documents to the system prompt of the agent
* `ChatNode`: Run the inference and collect the LLM response
* `ToolNode`: If the agent has tool attached, the model can ask their execution eventually.

Finally the RAG will return the LLM message to you.


# Data loader

Learn how to create data loader pipelines to feed your RAG applications.

{% hint style="info" %}
PREREQUISITES

This guide assumes you are already familiar with RAG. Check out the dedicated documentation: <https://docs.neuron-ai.dev/rag>
{% endhint %}

To build a structured AI application you need the ability to convert all the information you have into text, so you can generate embeddings, save them into a vector store, and then feed your Agent to answer the user's questions.

<figure><img src="/files/gXvl8JZ77R5GokTBMDBB" alt=""><figcaption></figcaption></figure>

Neuron gives you several tools (data loaders) to simplify this process.

```php
use App\Neuron\MyRAG;
use NeuronAI\RAG\DataLoader\FileDataLoader;

MyRAG::make()->addDocuments(
    // Use the file data loader component to process a text file
    FileDataLoader::for(__DIR__.'/my-article.md')->getDocuments()
);
```

Using the Neuron toolkit you can create data loading pipelines with the benefits of unified interfaces to facilitate interactions between components, like embedding providers, vector store, and file readers.

## FileDataLoader

If you need to extract text from files the `FileDataLoader` allows you to process any simple text document.

```php
use NeuronAI\RAG\DataLoader\FileDataLoader;

// Read a file and get "documents"
$documents = FileDataLoader::for(__DIR__.'/my-article.md')->getDocuments();

// Pass a directory to process all files
$documents = FileDataLoader::for(__DIR__)->getDocuments();
```

By default `FileDataLoader` read the content of a file as it is in the file system, but not all file type are ready to be treated as simple text. Neuron provides you with the ReaderInterface and several pre-defined reader components for the most common file formats.

Notice that each file reader is associated to a file extension. So based on the input file extension the data loader will automatically use the appropriate reader.

### PDF Reader

{% hint style="warning" %}
To use `PdfReader` you need to install the [**poppler**](https://en.wikipedia.org/wiki/Pdftotext) utility.
{% endhint %}

```php
use NeuronAI\RAG\DataLoader\FileDataLoader;

// Register the PDF reader
$documents = FileDataLoader::for(__DIR__)
    ->addReader('pdf', new \NeuronAI\RAG\DataLoader\PdfReader())
    ->getDocuments();
```

### HTML to Markdown Reader

{% hint style="warning" %}
To use `HtmlReader` you need to install the [**html2text**](https://github.com/mtibben/html2text) composer package.
{% endhint %}

```php
use NeuronAI\RAG\DataLoader\FileDataLoader;

// Register the PDF reader
$documents = FileDataLoader::for(__DIR__)
    ->addReader(['html', 'xhtml'], new \NeuronAI\RAG\DataLoader\HtmlReader())
    ->getDocuments();
```

### StringDataLoader

If you are already getting text from your database or other sources, you can use the StringDataLoader to convert this text into documents, ready to be embedded and stored by the other Neuron components in the chain:

```php
use App\Neuron\MyRAG;
use NeuronAI\RAG\DataLoader\StringDataLoader;

$contents = [
    // list of strings (text you want to embed)
];

foreach ($contents as $text) {
    $documents = StringDataLoader::for($text)->getDocuments(); 
    
    MyRAG::make()->addDocuments($documents);
}
```

### Document meta-data

After getting the array of documents from a data loader you can eventually attach custom meta-data to the document that will be saved in the vector store along with other document default fields:

```php
$documents = FileDataLoader::for($directory)->getDocuments(); 

foreach($documents as $document) {
    $document->addMetadata('user_id', 1234);
}

MyRAG::make()->addDocuments($documents);
```

Once you have these custom fields in the vector store you can use hybrid search for databases that support this feature.

{% hint style="info" %}
Hybrid search allows you to narrow the scope of a semantic search query against records that match certain criteria on other document fields rather that compare only the vector embeddings. Explore the [Vector Store section](/rag/vector-store) to know which database support hybrid search.
{% endhint %}

## Text Splitter

Neuron data loaders get files or text in input and generate an array of `\NeuronAI\RAG\Document` objects. These documents are embeddable units. The original text is split into smaller pieces of text to be converted into embeddings and saved in the vector store.

The logic data loaders use to split a long text into chunks can be customized using different strategies. Neuron has a dedicated component for this purpose called "Splitter", and it can be attached to the data loader based on the strategy you prefer or need:

```php
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new DelimiterTextSplitter()
    )
    ->getDocuments();
```

### DelimiterTextSplitter (default)

This is the default splitter for all data loaders.

```php
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new DelimiterTextSplitter(
            maxLength: 1000,
            separator: '.',
            wordOverlap: 0
        )
    )
    ->getDocuments();
```

Each of these parameters has an impact on the performance and accuracy of your RAG agent.

#### Max Length

Each chunk will not be longer than this value, and it will be divided into smaller documents eventually. The length can impact the accuracy of embeddings representations. The longer your units of text are, the less accurate the embeddings representation will be.

#### Separator

The text is first split into chunks based on a separator. By default the component uses the period character. You can eventually customize this separator by using any delimiter for your text.

#### Overlap

Sometimes it could be useful to bring words from the previous and next chunk into a document to increase the semantic connection between adjacent sections of the text. By default no overlap is applied.

### SentenceTextSplitter

Splits text into sentences, groups into word-based chunks, and optionally applies overlap in terms of words.

```php
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new SentenceTextSplitter(
            maxWords: 200,
            overlapWords: 0
        )
    )
    ->getDocuments();
```

**MaxWords**: maximum number of words per chunk

**OverlapWords**: number of overlapping words between chunks

### Implement Custom Splitters

You can implement a custom splitting logic implementing the `SplitterInterface`:

```php
namespace NeuronAI\RAG\Splitter;

use NeuronAI\RAG\Document;

interface SplitterInterface
{
    /**
     * @return Document[]
     */
    public function splitDocument(Document $document): array;

    /**
     * @param  Document[]  $documents
     * @return Document[]
     */
    public function splitDocuments(array $documents): array;
}
```

You can interact with external service or create your custom logic to split a long text into smaller chunks. Once you have created your custom implementation you can use it in with the data loaders:

```php
class CustomSplitter implements SplitterInterface
{
    public function splitDocument(Document $document): array
    {
        // Your logic here...
    }
    
    public function splitDocuments(array $documents): array
    {
        // Your logic here...
    }
}

// Use the custom splitter into the data loader pipeline
$documents = FileDataLoader::for($directory)
    ->withSplitter(
        new CustomSplitter()
    )
    ->getDocuments();
```

## Reindex Knowledge Source

Reindexing is a hot topic in RAG system design because the practice of breaking text into chunks makes it difficult to update individual pieces of information when the content of the original knowledge changes.

In Neuron The `Document` class is designed to carry some metadata to help you identify the source of each piece of knowledge stored into the vector database, like `sourceType` and `sourceName` fields. Using this information you can easily update the vector store with the updated version of the content from a file previously used as a source of knowledge.

{% hint style="warning" %}
The new version of the file **must have the same path and name** you used originally, otherwise the documents will be added as new ones.
{% endhint %}

```php
$documents = FileDataLoader::for("/path/to/directory")
    ->withSplitter(
        new SentenceTextSplitter(
            maxWords: 200,
            overlapWords: 0
        )
    )
    ->getDocuments();

// Reindex by sourceType and sourceName
MyRAG::make()->reindexBySource($documents);
```

If `sourceType` and `sourceName` of the Documents are already present into the vector store, they will be deleted and the Documents of the new version will be saved. Other documents will be stored as usual into the vector database.

## Use standalone components

In the examples below we used the RAG agent instance to process the final part of the ingestion pipeline: generate embeddings for document chunks, and store them into jthe vector database.

In alternative of take advantage of the RAG agent instance you can use the embedding provider and the vector store as standalone components. Remember that the vector store here must be same connected to the RAG agent.

```php
use App\Neuron\MyRAG;
use NeuronAI\RAG\DataLoader\FileDataLoader;
use NeuronAI\RAG\DataLoader\StringDataLoader;
use NeuronAI\RAG\EmbeddingProvider\OpenAIEmbeddingProvider;
use NeuronAI\RAG\VectorStore\FileVectorStore;

$embedder = new OpenAIEmbeddingProvider(
    key: 'OPENAI_API_KEY',
    model: 'OPENAI_MODEL'
);

$store = new FileVectoreStore(
    directory: __DIR__,
    key: 'demo'
);

// Process files and contents
$documents = FileDataLoader::for(__DIR__.'/documents');
    ->addReader('pdf', new \NeuronAI\RAG\DataLoader\PdfReader())
    ->getDocuments(); 

// Generate embeddings and store documents in the vector database
$store->addDocuments(
    $embedder->embedDocuments($documents)
);

```

With this simple process you can ingest GB of data into your vector store to feed your RAG agent.


# Embeddings Provider

Integrate services to transform text into vectors for semantic search.

Transform your text into vector representations! Embeddings let you add Retrieval-Augmented Generation ([RAG](/rag/rag)) into your AI applications.

## Available Embeddings Providers

The framework already includes the following embeddings provider.

### Ollama

With Ollama you can run embedding models locally. Documentation - <https://ollama.com/blog/embedding-models>

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OllamaEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OllamaEmbeddingsProvider(
            model: 'OLLAMA_EMBEDDINGS_MODEL'
        );
    }
}
```

### Voyage AI

Documentation - <https://www.voyageai.com/>

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\VoyageEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new VoyageEmbeddingsProvider(
            key: 'VOYAGE_API_KEY',
            model: 'VOYAGE_EMBEDDINGS_MODEL' // voyage-3-large
        );
    }
}
```

### OpenAI

Documentation - <https://platform.openai.com/docs/guides/embeddings>

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAIEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAIEmbeddingsProvider(
            key: 'OPENAI_API_KEY',
            model: 'OPENAI_EMBEDDINGS_MODEL' // text-embedding-3-small
        );
    }
}
```

### OpenAILikeEmbeddings

You can use any providers comaptible with OpenAI API format:

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\OpenAILikeEmbeddings;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OpenAILikeEmbeddings(
            baseUri: 'PRODIDER_URL',
            key: 'PROVIDER_API_KEY',
            model: 'PROVIDER_EMBEDDING_MODEL'
        );
    }
}
```

### Gemini

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\GeminiEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new GeminiEmbeddingsProvider(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_EMBEDDINGS_MODEL' // gemini-embedding-001
        );
    }
}
```

### Cohere

```php
namespace App\Neuron;

use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\CohereEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new CohereEmbeddingsProvider(
            key: 'COHERE_API_KEY',
            model: 'COHERE_EMBEDDINGS_MODEL' // embed-v4.0
        );
    }
}
```

### Aws Bedrock

```php
namespace App\Neuron;

use Aws\BedrockRuntime\BedrockRuntimeClient;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\Embeddings\AwsBedrockEmbeddingsProvider;
use NeuronAI\RAG\RAG;

class MyRAG extends RAG
{
    ...
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        $client = new BedrockRuntimeClient([
            'version' => 'latest',
            'region' => 'us-east-1',
            'credentials' => [
                'key' => 'AWS_BEDROCK_KEY',
                'secret' => 'AWS_BEDROCK_SECRET',
            ],
        ]);
        
        return new AwsBedrockEmbeddingsProvider(
            client: $client,
            model: 'AWS_EMBEDDINGS_MODEL'
        );
    }
}
```

## Implement a new Provider

To create a custom provider you just have to extend the `AbstractEmbeddingsProvider` class. This class already implement the framework specific methods and let's you free to implement the only provider specific HTTP call into the `embedText()` method:

```php
namespace App\Neuron\Embeddings;

use GuzzleHttp\Client;

class CustomEmbeddingsProvider extends AbstractEmbeddingsProvider
{
    protected Client $client;

    protected string $baseUri = 'HTTP-ENDPOINT';

    public function __construct(
        protected string $key,
        protected string $model
    ) {
        $this->client = new Client([
            'base_uri' => trim($this->baseUri, '/').'/',
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'Authorization' => 'Bearer ' . $this->key,
            ]
        ]);
    }

    public function embedText(string $text): array
    {
        $response = $this->client->post('', [
            'json' => [
                'model' => $this->model,
                'input' => $text,
            ]
        ]);

        $response = \json_decode($response->getBody()->getContents(), true);

        return $response['data'][0]['embedding'];
    }
}
```

You should adjust the HTTP request based on the APIs of the custom provider.


# Vector Store

Neuron provides you with ready to use components to connect your agent to vector databases.

We currently offer first-party support for the following vector store:

### Memory

This is an implementation of a volatile vector store that keeps your embeddings into the machine memory for the current session. It's useful when you don't need to store the generated embeddings for long term use, but just during current interaction sessions (or for local use).

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\MemoryVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new MemoryVectorStore();
    }
}
```

### File

File storage could be useful for low volume use case or local and staging environments. Embedded documents will be stored in the file system and processed during similarity search.

`FileVectorStore` uses PHP generators to read the embedded documents from the file systems. It will never keep more than `topK` items in memory while iterating very fast. You can store thousands of documents in your local filesystem only taking care on the maximum time you can accept to perform the similarity search.

You can also use this component to release agents with some knowledge already incorporated in a file.

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\FileVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(
            directory: storage_path(),
            topK: 4
        );
    }
}
```

### PHPVector

PHPVector adapter on top of [`ezimuel/phpvector`](https://github.com/ezimuel/PHPVector). It is a pure-PHP vector database implementing **HNSW** (Hierarchical Navigable Small World) for approximate nearest-neighbour search and **BM25** for full-text retrieval. Both engines can be combined into a single **hybrid search** pipeline.

You can install the component with composer:

```shellscript
composer require neuron-core/php-vector
```

Use it in a RAG context:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\PHPVector\PHPVector;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyRAG extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new PHPVector(
            path: '/var/data/mydb',
            topK: 5,
        );
    }
}
```

### MariaDB

MariaDB supports VECTOR column type starting from version 11.7. To make this component works you need to create the table to store documents and related vectors. Here is the SQL script you can use to do so:

```sql
CREATE TABLE IF NOT EXISTS rag_documents (
    id UUID NOT NULL PRIMARY KEY,
    content TEXT,
    sourceType VARCHAR(255),
    sourceName VARCHAR(255),
    metadata JSON,
    embedding VECTOR(1536) NOT NULL,
    VECTOR INDEX (embedding)
)
```

Use it in a RAG context:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\MariaDBVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new MariaDBVectorStore(
            new \PDO(...), // Or get the PDO instance from the ORM
        );
    }
}
```

### Pinecone

Pinecone makes it easy to provide long-term memory for high-performance AI applications. It’s a managed, cloud-native vector database with a simple API and no infrastructure hassles. Pinecone serves fresh, filtered query results with low latency at the scale of billions of vectors.

Here is how to use Pinecone in your agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\PineconeVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new PineconeVectorStore(
            key: 'PINECONE_API_KEY',
            indexUrl: 'PINECONE_INDEX_URL'
        );
    }
}
```

Pinecone also supports hybrid search that allows you to filter documents not only by similarity with the input prompt, but also by metadata stored along with your documents. You can pass additional filters to your agent instance so Pinecone will take them in consideration while filtering documents.

You can add the `addVectorStoreFilters()` method to your agent class to pass down filters at runtime:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\PineconeVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    protected array $vectorStoreFilters = [];

    ...

    protected function vectorStore(): VectorStoreInterface
    {
        $store = new PineconeVectorStore(
            key: 'PINECONE_API_KEY',
            indexUrl: 'PINECONE_INDEX_URL'
        );

        return $store->withFilters($this->vectorStoreFilters);
    }

    public function addVectorStoreFilters(array $filters): self
    {
        $this->vectorStoreFilters = $filters;
        return $this;
    }
}
```

When you run your agent you can pass filters on the fly:

```php
$response = MyRAG::make()
    ->addVectorStoreFilters([
        // Add filters
    ])
    ->chat(new UserMessage(...))
    ->getMessage();
```

Take a look at the Pinecone official documentation to better understand the metadata filters: <https://docs.pinecone.io/reference/api/2025-04/data-plane/query#body-filter>

### Weaviate

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\WeaviateVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new WeaviateVectorStore(
            collection: 'WEAVIATE_COLLECTION_NAME',
            host: 'http://localhost:8080', // Local or cloud URL
            key: 'WEAVIATE_KEY' // optional for local deployment
        );
    }
}
```

### Elasticsearch

Elasticsearch's open source vector database offers an efficient way to create, store, and search vector embeddings. To use Elasticseach as a vector store in your agents implementation you have to import the official client:

```bash
composer require elasticsearch/elasticsearch
```

Here is how to create a RAG that uses Elasticsearch:

```php
namespace App\Neuron;

use Elastic\Elasticsearch\ClientBuilder;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\ElasticsearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        $elasticsearch = ClientBuilder::create()
           ->setHosts(['<elasticsearch-endpoint>'])
           ->setApiKey('<api-key>')
           ->build();
       
        return new ElasticsearchVectorStore(
            client: $elasticsearch,
            index: 'neuron-ai'
        );
    }
}
```

Elasticsearch also support hybrid search. You can pass additional filters to your agent instance so Elasticsearch will take them in consideration while filtering documents.

You can add the `addVectorStoreFilters()` method to your agent class to pass down filters at runtime:

```php
namespace App\Neuron;

use Elastic\Elasticsearch\Client;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\ElasticsearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    protected array $vectorStoreFilters = [];

    ...

    protected function vectorStore(): VectorStoreInterface
    {
        // Create the client
        $elasticsearch = ClientBuilder::create()
           ->setHosts(['<elasticsearch-endpoint>'])
           ->setApiKey('<api-key>')
           ->build();
           
        // Create the store
        $store = new ElasticsearchVectorStore(
            client: $this->elasticsearch,
            index: 'neuron-ai'
        );

        // Apply filters
        return $store->withFilter($this->vectorStoreFilters);
    }

    public function addVectorStoreFilters(array $filters): self
    {
        $this->vectorStoreFilters = $filters;
        return $this;
    }
}
```

Pass filters dynamically at runtime:

```php
$response = MyRAG::make()
    ->addVectorStoreFilters([
        // Add filters
    ])
    ->chat(new UserMessage(...))
    ->getMessage();
```

### OpenSearch

Opensearch is the pure open source alternative to Elasticsearch. To use Opensearch in your agents you need to install its official client:

```bash
composer require opensearch-project/opensearch-php
```

Once you have the official client installed in your app you can return an instance of the `OpenSearchVectorStore` in your RAG agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\OpenSearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;
use OpenSearch\GuzzleClientFactory;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        $opensearch = new GuzzleClientFactory()->create([
            'base_uri' => 'http://localhost:9200',
        ]);
        
        return new OpenSearchVectorStore(
            client: $opensearch,
            index: 'neuron-ai',
        );
    }
}
```

### Typesense

[Typesense](https://typesense.org/) is an open source alternative to the options above. To use Typesense in your agents you need to install its official client:

```bash
composer require typesense/typesense-php
```

Once you have the official client installed in your app you can return an instance of the TypesenseVectorStore in your RAG agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\TypesenseVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        $typesense = new \Typesense\Client([
            'api_key' => 'TYPESENSE_API_KEY',
            'nodes' => [
                [
                    'host' => 'TYPESENSE_NODE_HOST',
                    'port' => 'TYPESENSE_NODE_PORT',
                    'protocol' => 'TYPESENSE_NODE_PROTOCOL'
                ],
            ]
        ]);
        
        return new TypesenseVectorStore(
            client: $typesense,
            collection: 'neuron-ai',
            vectorDimension: 1024
        );
    }
}
```

### Qdrant

[Qdrant](https://qdrant.tech/) is an open source vector database with strong similarity search capabilities. To use Qdrant in your agents you have to provide a `collectionUrl`. This means you will first need to create a collection on Qdrant with its attributes like: name, similarity search algorithm, vector dimension, etc.

Once you have the collection URL you can attach the `QdrantVectorStore` instance to your agent.

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\QdrantVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new QdrantVectorStore(
            collectionUrl: 'http://localhost:6333/collections/neuron-ai/',
            key: 'QDRANT_API_KEY'
        );
    }
}
```

### ChromaDB

[Chroma](https://trychroma.com/) is an open source database designed to be an AI application data source. To use ChromaDB in your agents you have to provide the name of an internal collection where you want to store the embeddings.

Once you have the collection created on your Chroma instance you can attach the `ChromaVectorStore` instance to the agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\ChromaVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new ChromaVectorStore(
            collection: 'neuron-ai',
            //host: 'http://localhost:8000', <-- This is by default
            topK: 5
        );
    }
}
```

### Meilisearch

[Meilisearch](https://www.meilisearch.com/) is a hybrid search engine, but the Neuron implementation uses it exclusively as a vector store for embeddings and similarity search.

The `indexUid` parameter should be the identifier of a Meilisearch index that you have created and configured. Make sure this index defines a vector field whose dimension matches the embedding size produced by the embedder you are using. The `embedder` value (for example, `default`) must correspond to a named embedder configured in your Neuron setup so that the stored vectors and the index configuration stay aligned. Add the component to your RAG:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\VectorStore\MeilisearchVectorStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...

    protected function vectorStore(): VectorStoreInterface
    {
        return new MeilisearchVectorStore(
            indexUid: 'MEILISEARCH_INDEXUID',
            host: 'http://localhost:8000', // Or use the cloud URL
            key: 'MEILISEARCH_API_KEY',
            embedder: 'default',
            topK: 5
        );
    }
}
```

### Implement custom Vector Stores

If you want to create a new provider you have to implement the `VectorStoreInterface` interface:

```php
namespace NeuronAI\RAG\VectorStore;

use NeuronAI\RAG\Document;

interface VectorStoreInterface
{
    public function addDocument(Document $document): void;

    /**
     * @param  Document[]  $documents
     */
    public function addDocuments(array $documents): void;

    public function deleteBySource(string $sourceName, string $sourceType): void;

    /**
     * Return docs most similar to the embedding.
     *
     * @param  float[]  $embedding
     * @return Document[]
     */
    public function similaritySearch(array $embedding, int $k = 4): iterable;
}
```

There are two different methods for adding a single document or a collection of documents because many databases provide different APIs for these use cases. If the database you want to interact to doesn't handle these requests differently you can implement `addDocument()` as a placeholder.

The similaritySearch should return documents with a similarity score not a similarity distance. If the underlying database returns a distance you can convert it to a score using the utility class `VectorSimilarity`:

```php
namespace App\Neuron\VectorStore;

use NeuronAI\RAG\Document;
use NeuronAI\RAG\VectorSimilarity;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyVectorStore implements VectorStoreInterface
{
    ...


    /**
     * @param float[] $embeddings
     */
    public function similaritySearch(array $embedding): iterable
    {
        $documents = // get documents from the vector store

        return \array_map(function (Document $document) {
            return $document->setScore(
                VectorSimilarity::similarityFromDistance($similarity)
            );
        }, $documents);
    }
}
```

This is the basic template for a new AI provider implementation.

```php
namespace App\Neuron\VectorStore;

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use NeuronAI\RAG\Document;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyVectorStore implements VectorStoreInterface
{
    protected Client $client;

    public function __construct(
        string $key,
        protected string $index,
        protected int $topK = 5
    ) {
        $this->client = new Client([
            'base_uri' => 'https://api.vector-store.com',
            'headers' => [
                'Accept' => 'application/json',
                'Content-Type' => 'application/json',
                'Authorization' => "Bearer {$key}",
            ]
        ]);
    }

    public function addDocument(Document $document): void
    {
        $this->addDocuments([$document]);
    }

    /**
     * @param Document[] $documents
     */
    public function addDocuments(array $documents): void
    {
        $this->client->post("indexes/{$this->index}", [
            RequestOptions::JSON => \array_map(function (Document $document) {
                return [
                    'vector' => $document->embedding,
                ];
            }, $documents)
        ]);
    }

    /**
     * @return Document[]
     */
    public function similaritySearch(array $embedding): iterable
    {
        // perform similarity search and return an array of Document objects
    }
}
```

After creating your own implementation you can use it in the agent:

```php
namespace App\Neuron;

use App\Neuron\VectorStore\MyVectorStore;
use NeuronAI\Agent;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyAgent extends Agent
{
    protected function vectorStore(): VectorStoreInterface
    {
        return new MyVectorStore(
            key: 'VECTORSTORE_API_KEY',
            index: 'neuron-ai',
        );
    }
}
```

{% hint style="warning" %}
We strongly recommend you to submit new vector store implementations via PR on the official repository or using other [Inspector.dev](https://inspector.dev/developer-support/) support channels. The new implementation can receives an important boost in its advancement by the community.
{% endhint %}


# Pre/Post Processor

Improve the RAG output by pre/post processing prompts and retrieval results.

As with most software systems, RAG is easy to use but hard to master. The truth is that there is more to RAG than putting documents into a vector DB and adding an LLM on top. That *can work*, but it won't always.

With RAG, you are performing a *semantic search* across many text documents — these could be tens of thousands up to tens of billions of documents.

To ensure fast search times at scale, we typically use vector search — that is, we transform our text into vectors, place them all into a vector database, and compare their proximity to a query using a similarity algorithm (like cosine similarity).

To achieve high quality responses from the RAG agent you can work on two parts of the retrieval process:

1. Optimize the user prompt (*Pre-Processors*)
2. Refine the search results gathered from the vector store (*Post-Processors*)

## Pre-Processors

Rather than treating the user's original query as the final word, the pre-processor views it as the starting point for a more sophisticated interaction with the underlying knowledge system. This isn't about second-guessing the user's intent, but about recognizing that their natural language expression often contains multiple embedded questions, implicit constraints, and contextual assumptions that need to be unpacked and reformulated to maximize retrieval effectiveness.

Consider the complexity hidden within seemingly simple queries. When someone asks "Why did our sales drop last quarter?", they're actually expressing a multi-faceted information need that might require understanding seasonal trends, competitor activities, marketing campaign effectiveness, product performance metrics, and economic indicators. A naive RAG system might retrieve general information about sales analysis, missing the opportunity to provide comprehensive, contextually relevant insights that address the full scope of the underlying question.

### Query Transformation

The core of this pattern is to use an LLM to transform the original question into a more structured prompt that the main RAG agent can use to perform a more accurate and effective document retrieval from the vector store.

Working with Neuron you can pass the instance of the AI provider already attached to your agent:

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PreProcessor\QueryTransformationPreProcessor;
use NeuronAI\RAG\PreProcessor\QueryTransformationType;

class MyChatBot extends RAG
{
    ...

    protected function preProcessors(): array
    {
        return [
            new QueryTransformationPreProcessor(
                provider: $this->resolveProvider(),
                transformation: QueryTransformationType::REWRITING,
            ),
        ];
    }
}
```

Or use a different provider among the supported AI providers like Gemini, Ollama, OpenAI, HuggingFace, etc.

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PreProcessor\QueryTransformationPreProcessor;
use NeuronAI\RAG\PreProcessor\QueryTransformationType;

class MyChatBot extends RAG
{
    ...

    protected function preProcessors(): array
    {
        return [
            new QueryTransformationPreProcessor(
                // Use one of the supported AI Provider
                provider: new Anthropic(
                    key: 'ANTHROPIC_API_KEY',
                    model: 'ANTHROPIC_MODEL',
                ),
                transformation: QueryTransformationType::REWRITING,
            ),
        ];
    }
}
```

The three core strategies implemented in the Neuron pre-processor are: rewriting, decomposition, and HyDE (Hypothetical Document Embeddings), each tackle different aspects of this query transformation challenge.

**Query rewriting** addresses the fundamental mismatch between conversational language and search-optimized formulations. When users express their needs in casual, context-dependent language, the rewriting process translates these expressions into more precise, searchable formulations that better align with how information is typically organized and indexed.

**Decomposition** handles the reality that complex questions often contain multiple distinct information needs that would be better served by separate retrieval operations. Rather than forcing a single search to satisfy multiple different aspects of a query, decomposition breaks down complex questions into their constituent parts, allowing each component to be addressed with focused precision before synthesizing the results into a comprehensive response.

T**he HyDE approach** represents perhaps the most sophisticated strategy, working backwards from the assumption that the best way to find relevant information is to first imagine what that information might look like. Instead of searching directly with the user's question, HyDE generates hypothetical documents that would ideally answer the query, then uses these generated documents as the basis for similarity searches. This approach is particularly powerful when dealing with abstract concepts or when the user's terminology doesn't closely match the vocabulary used in the source documents.

## Post-Processors

For vector search to work instead, we need vectors. These vectors are essentially compressions of the "meaning" behind some text into (typically) 768 or 1536-dimensional vectors. There is some information loss because we're compressing this information into a single vector.

Because of this information loss, we often see that the top three (for example) vector search documents will miss relevant information. Unfortunately, the retrieval may return relevant information below our `top_k` cutoff.

What do we do if relevant information at a lower position would help our LLM formulate a better response? The easiest approach is to increase the number of documents we're returning (increase `top_k`) and pass them all to the LLM.

Unfortunately, we cannot pass everything to the LLM because this dramatically reduces the LLM's performance to find relevant information from the text placed within its context window.

The solution to this issue is retrieving plenty of documents from the vector store and then *minimizing* the number of documents that make it to the LLM. To do that, you can reorder and filter retrieved documents to keep just the most relevant for our LLM.

Neuron allows you to define a list of post-processor components to pipe as many transformations you need to optimize the agent output.

### Rerankers

Reranking is one of the most popular post-process operations you can apply to the retrieved documents. A reranking service calculates a similarity score of each documents retrieved from the vector store with the input query.

We use this score to reorder the documents by relevance and take only the most useful.

### Jina Reranker

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PostProcessor\JinaRerankerPostProcessor;
use NeuronAI\RAG\VectorStore\FileVectoreStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectoreStore(
            directory: storage_path(),
            topK: 50
        );
    }

    protected function postProcessors(): array
    {
        return [
            new JinaRerankerPostProcessor(
                key: 'JINA_API_KEY',
                model: 'JINA_MODEL',
                topN: 5
            ),
        ];
    }
}
```

In the example above you can see how the vector store is instructed to get 50 documents, and the reranker will basically take only the 5 most relevant ones.

### Cohere Reranker

```php
namespace App\Neuron;

use NeuronAI\RAG\RAG;
use NeuronAI\RAG\PostProcessor\CohereRerankerPostProcessor;
use NeuronAI\RAG\VectorStore\FileVectoreStore;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class MyChatBot extends RAG
{
    ...
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectoreStore(
            directory: storage_path(),
            topK: 50
        );
    }

    protected function postProcessors(): array
    {
        return [
            new CohereRerankerPostProcessor(
                key: 'COHERE_API_KEY',
                model: 'COHERE_MODEL',
                topN: 3
            ),
        ];
    }
}
```

### Fixed Threshold

It uses a simple, configurable fixed threshold to filter documents. Documents with scores below the threshold are removed from results.

It's ideal for scenarios requiring an explicit score cutoff for fixed quality requirements.

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\FixedThresholdPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new FixedThresholdPostProcessor(
                threshold: 0.5
            ),
        ];
    }
}
```

### Adaptive Threshold

It implements a dynamic thresholding algorithm using median and MAD (Median Absolute Deviation). It automatically adjusts to score distributions, making it robust against outliers.

You can configure a multiplier parameter that controls filtering aggressiveness.

Recommended multiplier values:

* \[0.2 to 0.4] High precision mode. For more targeted results with fewer but more relevant documents.
* \[0.5 to 0.7] Balanced mode. Recommended setting for general use cases.
* \[0.8 to 1.0] High recall mode. For more inclusive results that prioritize coverage.
* \>1.0 Not recommended as it tends to include almost all documents.

This component is ideal for cleaning up RAG results with dynamic filtering that adapts to the current result set's score distribution.

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\AdaptiveThresholdPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new AdaptiveThresholdPostProcessor(
                multiplier: 0.6
            ),
        ];
    }
}
```

### LocalAI Reranker

[LocalAI](https://localai.io/) is an all-in-one complete AI stack. You can run large language models locally on your hardware. It provides an OpenAI compatible API for LLMs, so you can use it with the [OpenAILike](/providers/ai-provider#openailike) provider.

```php
namespace App\Neuron;

use NeuronAI\RAG\PostProcessor\LocalAIPostProcessor;

class MyChatBot extends RAG
{
    ...

    protected function postProcessors(): array
    {
        return [
            new LocalAIPostProcessor(
                key: 'LOCALAI_KEY',
                model: 'LOCALAI_MODEL',
                topN: 3,
                host: 'LOCALAI_HOST' // "https://localhost:8080" by default
            ),
        ];
    }
}
```

## Monitoring

Neuron built-in observability features automatically trace the execution of each post processor, so you'll be able to monitor interactions with external services in your [Inspector](https://inspector.dev/) account. Learn more in the [monitoring section](/agent/observability).

<figure><img src="/files/DfF2NyneTmLVSDSFnVvC" alt=""><figcaption></figcaption></figure>

## Extending The Framework

With Neuron you can easily create your custom post processor components by simply extending the `\NeuronAI\PostProcessor\PostProcessorInterface`:

```php
namespace NeuronAI\RAG\PostProcessor;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\RAG\Document;

interface PostProcessorInterface
{
    /**
     * Process an array of documents and return the processed documents.
     *
     * @param Message $question The question to process the documents for.
     * @param array<Document> $documents The documents to process.
     * @return array<Document> The processed documents.
     */
    public function process(Message $question, array $documents): array;
}
```

Implementing the `process` method you can perform actions on the list of documents and return the new list. Neuron will run the post processors in the same order they are listed in the `postProcessors()` method.

Here is a practical example:

```php
namespace App\Neuron\PostProcessors;

use NeuronAI\Chat\Messages\Message;
use NeuronAI\RAG\PostProcessor\PostProcessorInterface;

// Implement your custom component
class CutOffPostProcessor implements PostProcessorInterface
{
    public function __constructor(protected int $level) {}

    public function process(Message $question, array $documents): array
    {
        /*
         * Apply a cut off on the score returned by the vector store
         */
         
        return $documents;
    }
}
```


# Retrieval

Implement custom retrieval strategies

### Introduction

The RAG module has a separate retrieval component that allows you to implement different strategies to accomplish context retrieval from external data sources. By default, RAG uses `SimilarityRetrieval` that simply queries the vector store to retrieve documents:

```php
namespace App\Neuron;

use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\RAG\Embeddings\EmbeddingsProviderInterface;
use NeuronAI\RAG\RAG;
use NeuronAI\RAG\RAG\Retrieval\RetrievalInterface;
use NeuronAI\RAG\RAG\Retrieval\SimilarityRetrieval;
use NeuronAI\RAG\VectorStore\VectorStoreInterface;

class WorkoutTipsAgent extends RAG
{
    protected function retrieval(): RetrievalInterface
    {
        return new SimilarityRetrieval(
            $this->resolveVectorStore(),
            $this->resolveEmbeddingsProvider()
        );
    }
    
    protected function provider(): AIProviderInterface
    {
        // Return an instance of an AI provider...
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        // Return an embeddings provider instance...
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        // Return a vector store instance...
    }
}
```

Implementing `RetrievalInterface` you are free to create any custom retrieval behaviour for your RAG.

```php
interface RetrievalInterface
{
    /**
     * Retrieve relevant documents for the given query.
     *
     * @return Document[]
     */
    public function retrieve(Message $query): array;
}
```

If you are implementing custom workflow you can use retrieval as a standalone component to dynamically retrieve context data for use in your agentic systems.

### Retrieval as a Tool

Neuron provides you with a built-in `RetrievalTool` tool that enables AI agents to perform context retrieval from vector stores if the model think it needs more context to answer the current user question. It's built on top of the `RetrievalInterface` , making it possible to build agents with on-demand RAG (Retrieval Augmented Generation) capabilities instead of the authomatic context injection provided by the RAG component.

Here is an example using the built-in `SimilarityRetrieval`:

```php
use NeuronAI\Tools\Toolkits\RetrievalTool;
use NeuronAI\RAG\Retrieval\SimilarityRetrieval;

class AgenticRAG extends Agent
{
    protected function provider(): AIProviderInterface
    {...}
    
    protected function instructions(): string
    {...}
    
    protected function tools(): array
    {
        return [
            RetrievalTool::make(
                new SimilarityRetrieval(
                    $this->vectorStore(), 
                    $this->embeddings()
                )
            ),
        ];
    }
    
    protected function vectorStore(): VectorStoreInterface
    {
        return new FileVectorStore(__DIR__);
    }
    
    protected function embeddings(): EmbeddingsProviderInterface
    {
        return new OllamaEmbeddingsProvider(
            model: 'OLLAMA_EMBEDDINGS_MODEL'
        );
    }
}
```

As you can notice in this example we don't extend RAG but the basic Agent instead. In this implementation we let the model decide if it's the case to search an external source to answer the user question.

You can always use all the tool and agent methods to customize description, instructions, and prompts in general to make the model behave according to your use case.

### RAPTOR Retrieval Module

Most retrieval-augmented models work by breaking down documents into small chunks and retrieving only the most relevant ones. However, this approach has some limitations:

* **Loss of Context**: Retrieving only small, isolated chunks may miss the bigger picture especially for documents with long contexts.
* **Difficulty in Multi-Step Reasoning**: Some questions require information from multiple sections of a document.

**Use RAPTOR when:**

* Users ask open-ended questions that require comprehensive coverage
* Your domain involves complex topics where context matters as much as facts
* You need to handle queries about themes, trends, or relationships across documents

**Stick with traditional RAG when:**

* Users primarily need quick, specific fact retrieval
* Processing speed and token efficiency are critical constraints

Learn more about RAPTOR in the dedicated repository:

{% embed url="<https://github.com/neuron-core/raptor-retrieval>" %}


# Getting Started

Guide, moderate, and control your multi-agent system with human-in-the-loop.

### What is a Workflow

A workflow is an event-driven, node-based way to control the execution flow of an application.

Your application is divided into sections called Nodes which are triggered by Events, and themselves return Events which trigger further nodes. By combining nodes and events, you can create arbitrarily complex flows that encapsulate logic and make your application more maintainable and easier to understand.

A node can be anything from a single line of code to a complex agent. It can have arbitrary inputs and outputs, which are passed around by Events. It's like n8n at code level.

<figure><img src="/files/rvMPhX7fi49RTKdiqSRQ" alt=""><figcaption></figcaption></figure>

Workflow allows you to use all the Neuron components like AI providers, embeddings, data loaders, chat history, vector store, etc, as standalone components to create totally customized agentic entities.

Agent and RAG classes are workflows themselves. They represent ready to use implementations of the most common patterns when it comes to tool calls, retrieval, structured output, etc. Workflow allows you to program your agentic system completely from scratch. Agent and RAG can be used inside a Workflow to complete tasks as any other component if you need to perform AI operations during execution.

What makes Neuron Workflow special is its **streaming** and **interruption** capabilities. This means your multi agent system can stream updates directly to clients, pause mid-process, ask for human input, wait for feedback, and then continue exactly where it left off – even if that's hours or days later.

### Why Use Workflows Instead of Regular Scripts?

You might be thinking: "This sounds great, but why can't I just write a regular PHP script with some if-statements and functions?" It's a fair question, and one I heard a lot while building Neuron. The answer becomes clear when you consider what happens when your process needs to go vs multiple branches, run them concurrently, implementing several loops and intermediate checkpoints, streamimng real-time updates to the client, or even pause, wait, and resume.

When you are at the beginning, and your use case is yet quite simple you couldn't see the real potential of Workflow, and it's normal. Keep in mind that if things hit the fan, Neuron already has the appropriate architecture to help you scale at any level.

### Development Benefits

From a developer perspective, Workflows solve several painful problems:

**Model and maintain complex scenario**: With these simple building blocks you will be able to create simple processes with a few steps, up to complex workflows with iterative loops and intermediate checkpoints.

**Human in the Loop**: Seamlessly incorporates human oversight. You can deploy AI in sensitive areas because humans are always in the loop for critical decisions.

**Streaming**: You can send real-time updates to the client during workflow execution.

**Debugging with inspector**: Instead of wondering why your workflow made a particular decision, you can see exactly what's happening on any node.

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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


# Single Step Workflow

How to create the first workflow with a single node

### Create a Workflow

A workflow is usually implemented as a class that inherits from Workflow. The class can define an arbitrary number of nodes, each of which is a class that extens `NeuronAI\Workflow\Node`.

First, let's create the MyWorkflow class:

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

```bash
vendor/bin/neuron make:workflow App\\Neuron\\MyAgent
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:workflow App\Neuron\MyAgent
```

{% endtab %}
{% endtabs %}

Here is the simplest possible workflow:

```php
namespace App\Neuron;

use NeuronAI\Workflow\Workflow;

class MyAgent extends Workflow
{
    protected function nodes(): array
    {
        return [
            new InitialNode(),
        ];
    }
}
```

Now let's create the node:

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

```bash
vendor/bin/neuron make:node App\\Neuron\\InitialNode
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:node App\Neuron\InitialNode
```

{% endtab %}
{% endtabs %}

A Node is an invokable class to handle an incoming event, and return another event:

```php
namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\Events\StartEvent;
use NeuronAI\Workflow\Events\StopEvent;
use NeuronAI\Workflow\WorkflowState;

class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): StopEvent
    {
        $state->set('answer', 'Hello World!');
        
        return new StopEvent();
    }
}
```

This will print "Hello World!" to the console:

```php
$finalState = MyWorkflow::make()->init()->run();

echo $finalState->get('answer'); // Print Hello World!
```

In this code we:

{% stepper %}
{% step %}
Define a class `MyWorkflow` that inherits from `Workflow`
{% endstep %}

{% step %}
Define a Node implementing the `__invoke` method
{% endstep %}

{% step %}
The step takes an event as input, which is an instance of `StartEvent`
{% endstep %}

{% step %}
The Node adds a value to the state and returns a `StopEvent`
{% endstep %}

{% step %}
We create an instance of `MyWorkflow`
{% endstep %}

{% step %}
We start the workflow and get the result
{% endstep %}

{% step %}
Print the result in the console
{% endstep %}
{% endstepper %}

<figure><img src="/files/rvMPhX7fi49RTKdiqSRQ" alt=""><figcaption></figcaption></figure>

### Start and Stop events

`StartEvent` and `StopEvent` are special events that are used to start and stop a workflow. The node that accepts a StartEvent will be triggered first by when you start the workflow. Returning a StopEvent will end the execution of the workflow and return the final state, even if other nodes remain un-executed.

### Type hint for events

The $event types (e.g. `StartEvent`) guide the Workflow execution. The expected return types of a node determine what node will be triggered next.

Event types are validated at compile time, so you will get an error message if for instance you return an event that is never consumed by another Node.

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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


# Multi Step Workflow

Learn how to handle complex execution flow orchestrating the execution of multiple nodes

Multiple steps are created by defining custom events that can be emitted by nodes and trigger other nodes. Let's define a simple 3-step workflow.

### Custom Events

We define two custom events, `FirstEvent` and `SecondEvent`. These classes can have any names and properties, but must implement `Event`:

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

```bash
vendor/bin/neuron make:event App\\Neuron\\FirstEvent
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:event App\Neuron\FirstEvent
```

{% endtab %}
{% endtabs %}

```php
namespace App\Neuron;

class FirstEvent implements Event 
{
    public function __construct(protected string $firstMsg){}
}

class SecondEvent implements Event 
{
    public function __construct(protected string $secondMsg){}
}
```

### Defining the workflow

Now we define the workflow itself. We do this by defining the input and output types on each node. Here is the minimal implementation of the nodes for the purpose of this demo.

<figure><img src="/files/69WmObamPhcKyXcMxTRI" alt=""><figcaption></figcaption></figure>

#### InitialNode

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

```bash
vendor/bin/neuron make:node App\\Neuron\\InitialNode
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:node App\Neuron\InitialNode
```

{% endtab %}
{% endtabs %}

```php
namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\StartEvent;
use App\Neuron\FirstEvent;

class InitialNode extends Node
{
    /**
     * Gets the "StartEvent" and returns "FirstEvent"
     */
    public function __invoke(StartEvent $event, WorkflowState $state): FirstEvent
    {
        echo "\n- Handling StartEvent";
        
        return new FirstEvent("InitialNode complete");
    }
}
```

#### NodeOne

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

```bash
vendor/bin/neuron make:node App\\Neuron\\NodeOne
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:node App\Neuron\NodeOne
```

{% endtab %}
{% endtabs %}

```php
class NodeOne extends Node
{
    /**
     * Takes "FirstEvent" as input and returns "SecondEvent"
     */
    public function __invoke(FirstEvent $event, WorkflowState $state): SecondEvent
    {
        echo "\n- ".$event->firstMsg;
        
        return new SecondEvent("NodeOne complete");
    }
}
```

#### NodeTwo

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

```bash
vendor/bin/neuron make:node App\\Neuron\\NodeTwo
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:node App\Neuron\NodeTwo
```

{% endtab %}
{% endtabs %}

```php
class NodeTwo extends Node
{
    /**
     * Takes "SecondEvent" as input and returns "StopEvent"
     */
    public function __invoke(SecondEvent $event, WorkflowState $state): StopEvent
    {
        echo "\n- ".$event->secondMsg;
        
        echo "\n- NodeTwo complete";
        
        return new StopEvent();
    }
}
```

Define the Workflow attaching the nodes:

```php
use NeuronAI\Workflow\Workflow;

$handler = Workflow::make()
    ->addNodes([
        new InitialNode(),
        new NodeOne(),
        new NodeTwo(),
    ])
    ->init();

/*
 * Run the workflow
 */
$handler->run();
```

The full output will be:

```
- Handling StartEvent
- InitialNode complete
- NodeOne complete
- NodeTwo complete
```

Of course there is still not much point to a workflow if you just run through it from beginning to end! Let's do some branching and looping.

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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


# Loops & Branches

Workflow makes branching and looping logic easy to implement thanks to its event driven design. Once you understand how nodes belong to events, it's easy to start imagining how you can create loops and branching, which is just deciding which event should be returned in an "if condition" or whatever logic.

## Loops

To create a loop, simply return the entry event of a previous node as the exit event of the current node. You can also use the same entry event as the current node's exit event to loop over the current node.

Take a look at the example below. The `NodeOne` can have two events as return type, `FirstEvent` and `SecondEvent`. If the node returns FirstEvent it will cause another execution of the same node because FirstEvent is handled by itself, creating a loop.

If the node returns `SecondEvent` it will finally move forward the execution to another node.

```php
class NodeOne extends Node
{
    public function __invoke(FirstEvent $event, WorkflowState $state): FirstEvent|SecondEvent
    {
        echo "\n- ".$event->firstMsg;
        
        if (rand(0, 1) === 1) {
            // Returning FirstEvent it will trigger another execution of NodeOne
            return new FirstEvent("Running a loop on NodeOne");
        }
        
        return new SecondEvent("NodeOne complete, move forward");
    }
}
```

{% hint style="warning" %}
Notice the node has now two return types for the `__invoke` method: `FirstEvent` and `SecondEvent`. You have to declare all possible return events on the method signature to let the Workflow build the execution chain.
{% endhint %}

Returning FirstEvent will trigger another execution of `NodeOne`. So the final output could be:

```php
$state = Workflow::make()
    ->addNodes([
        new InitialNode(),
        new NodeOne(),
        new NodeTwo()
    ])
    ->init()
    ->run();

/*
- Handling StartEvent
- InitialNode complete
- Running a loop on NodeOne
- Running a loop on NodeOne
- NodeOne complete, move forward
- NodeTwo complete
*/
```

You can create a loop from any node to any other node in the workflow by defining the appropriate input event and return events of the invoke method.

<figure><img src="/files/G4vtAws7h4WzCaDFoGSM" alt=""><figcaption></figcaption></figure>

The `NodeOne` can even return a StartEvent to jump right to the first node of the Workflow. The event driven architecutre allows you to directly point any node in the workflow both forward and backward.

## Branches

As you've already seen, you can conditionally return different events from a node to define custom execution flows. In this section we'll see an example of a workflow that branches into two different paths.

First let's create some custom events:

```php
namespace App\Neuron;

class BrancheA1Event implements Event 
{
    public function __construct(protected string $firstMsg){}
}

class BrancheA2Event implements Event 
{
    public function __construct(protected string $secondMsg){}
}

class BrancheB1Event implements Event 
{
    public function __construct(protected string $secondMsg){}
}

class BrancheB2Event implements Event 
{
    public function __construct(protected string $secondMsg){}
}
```

In the initial node of he workflow we decide what branched we want to go through. Remeber to always define the appropriate return types in the `__invoke` method signature:

```php
class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): BrancheA1Event|BrancheB1Event
    {
        if (rand(0, 1) === 1) {
            // Returning FirstEvent it will trigger another execution of NodeOne
            return new BrancheA1Event();
        }
        
        return new BrancheB1Event();
    }
}
```

The other nodes will move forward sequencially.

```php
$state = Workflow::make()
    ->addNodes([
        new InitialNode(),
        new A1Node(),
        new A2Node(),
        new B1Node(),
        new B2Node(),
    ])
    ->init()
    ->run();
```

You can of course combine branches and loops in any order to fulfill the needs of your application.

## Parallel Branches

<figure><img src="/files/9cfgO0KB8wW3LsqHCunF" alt=""><figcaption></figcaption></figure>

When you want to call the execution of multiple branches in parallel, you need to return the special event `ParallelEvent` from your node.

```php
use NeuronAI\Workflow\Events\ParallelEvent;

class DocumentProcessing extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): ParallelEvent
    {
        // Node logic here...
	
        // Finally return a ParallelEvent
        return new ParallelEvent([
            'text' => new TextProcessEvent(),
            'image' => new ImageProcessEvent(),
        ]);
    }
}
```

The `ParallelEvent` must be constructed with an array of `<branch_name> => <FirstInputEvent>`:

```php
new ParallelEvent([
	<branch_name> => <FirstInputEvent>
	...
])
```

Nodes handling the events you declare for each branch must be registered in the workflow:

```php
class MyWorkflow extends Workflow
{
	protected function nodes(): array
	{
		return [
			new DocumentProcessing(),
			
			// "text" branch
			new DescriptionGenerationNode(), // Handle TextProcessEvent
			new TextRefactorNode(),
			
			// "image" branch
			new ImageProcessNode(), // Handle ImageProcessEvent
			new AddWatermarkNode(),
			
			new MergeNode(),
		];
	}
}
```

A branch can be just one node, or a list of multiple nodes always connected with events.

### Handle the END of branches

The last node in your branch must return the framework built-in `StopEvent`.

In the example above `TextRefactorNode` and `AddWatermarkNode` will declare the end of their branch returning StopEvent:

```php
class AddWatermarkNode extends Node
{
    public function __invoke(TextProcessEvent $event, WorkflowState $state): StopEvent
    {
        // Node code here...
		
        // Returning StopEvent the branch ends
        return new StopEvent(result: 'Hello World!');
    }
}
```

Notice: StopEvent can also carry some results.

### Get the branches result

The `ParallelEvent` returned by the `DocumentProcessing` node is basically waiting the end of branches execution before being forwarded to the next node.

In the example above, the `MergeNode` is in charge to finally handle the `ParallelEvent`:

```php
class MergeNode extends Node
{
    public function __invoke(ParallelEvent $event, WorkflowState $state): StopEvent
    {
        $textBranchResult = $event->getResult('text');
        $imageBranchResult = $event->getResult('image');
        
        return new StopEvent();
    }
}
```

This node can read the final result of each branch with the `getResult()` method passing the `<branch_name>`.

As usual the merge node can stop the workflow, or return other events moving the workflow forward.

### Branch State Isolation

One detail worth noting: **each branch gets an isolated copy of the workflow state**. They start with the same snapshot, but mutations inside a branch don't propagate to sibling branches or to the main workflow. The only way to pass data back is through the `StopEvent` result.

This is intentional, it avoids a whole class of concurrency bugs where branches step on each other's state.

### AsyncExecutor

We also provide an implementation of the internal workflow executor that allows you to run multiple branches concurrently. To use the `AsyncExecutor` you need to install the [Amp](https://github.com/amphp/amp) package:

```bash
composer require amphp/amp
```

```php
class MyAgent extends Workflow 
{
    /**
     * Use the AsyncExecutor
     */
    protected function executor(): WorkflowExecutorInterface
    {
        return new AsyncExecutor();
    }

    protected function nodes(): array
    {
        return [...];
    }
}
```

This is particularly useful if you want to run multiple agentic tasks in parallel, since Neuron AI already provides the `AmpHttpClient` that you can inject into all components.

```php
use NeuronAI\HttpClient\AmpHttpClient;

class DescriptionGenerationNode extends Node
{
    public function __invoke(TextProcessEvent $event, WorkflowState $state): StopEvent
    {
        $input = new UserMessage('Describe this image');
        $input->addContent(
            new ImageContent(...)
        );

        $response = AsyncAgent::make()
            ->chat($input)
            ->getMessage();

        return new StopEvent(result: $response);
    }
}
```

## Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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


# Managing the State

Learn how to pass data around the workflow

Generally speaking, the purpose of a workflow is to get an initial state as an input, make able the nodes to manipulate this state during execution, and return the final state when the workflow completes.

### Workflow Input/Output

The final return value of the workflow itself is an instance of the workflow state. So, if you need to collect the result of the workflow execution, nodes must be able to write and read from the state until the workflow ends and return the final state to the parent script.

You can also provide an initial state to workflow to feed in input values.

```php
// 1. Provide an initial state as workflow input to feed in some data
$workflow = Workflow::make(state: new WorkflowState(['query' => 'Hi!']))
        ->addNode(new InitialNode())
        ->addNode(...)
        ->addNode(...);

// 2. Execute the workflow and get the final state
$finalState = $workflow->init()->run();

// 3. Use the final state data
echo $finalState->get('message');
```

### Using state in nodes

In our examples so far, we have passed data from node to node using properties of custom events. This is a powerful way to pass data around, but it has limitations. For example, if you want to pass data between steps that are not directly connected, you need to pass the data through all the nodes in between. This can make your code harder to read and maintain.

For this reasons we have the `WorkflowState` object available to every node in the workflow. To use it, the workflow inject the WorkflowState instance as the second argument of the node.

```php
namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\StartEvent;
use NeuronAI\Workflow\StopEvent;
use NeuronAI\Workflow\WorkflowState;

class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): StopEvent
    {
        $state->set('message', 'Hello World!');
        
        return new StopEvent();
    }
}
```

### Custom State

The default `WorkflowState` class is a bag to carry data during workflow execution. It might be useful to create a custom state class to define strictly typed properties for better code completion in your nodes, data validation, and general debugging.

Create a `CustomState` class to introduce custom properties:

```php
use App\Models\User;
use NeuronAI\Workflow\WorkflowState;

class CustomState extends WorkflowState
{
    protected User $user;
    
    public function setUser(User $user): CustomState
    {
        $this->user = $user;
        return $this;
    }
    
    public function getUser(): User
    {
        return $this->user;
    }
}
```

Nodes can accept an instance of `CustomState` instead of the default `WorkflowState`:

```php
class ExampleNode extends Node 
{
    public function __invoke(StartEvent $event, CustomState $state): StopEvent
    {
        // Use state properties in your nodes
        if ($state->getUser()->isAdmin()) {
            //...
        }
        
        return new StopEvent();
    }
}
```

Inject the `CustomState` on the workflow creation:

```php
$state = new CustomState();
$state->setUser($user);

$workflow = MyWorkflow::make(state: $state);

$finalState = $workflow->init()->run();
echo $finalState->getUser()->email;
```


# Interruption

The key breakthrough is that interruption isn't a bug, it's a feature.

### What it is

Neuron's interruption pattern provides a built-in *human-in-the-loop* mechanism that allows\
workflows to pause execution and wait for external input before resuming.

At its core, interruptions are implemented through the abstract `InterruptRequest` class, a framework primitive that developers can extend to create custom interruption experiences tailored to their\
application's specific needs. The framework includes an `ApprovalRequest` as a built-in implementation covering the most common use case of approving actions (such as tool calls), but the architecture is intentionally flexible: any workflow node or middleware can trigger an interruption, and the persistence layer ensures state is preserved across the pause/resume cycle, making it suitable for long-running processes that require human decision points at any stage.

If the built-in `ApprovalRequest` doesn't fit with your use case, you are free to create your custom interrutpion request to create a specific UI experience.

Here's how it works:

**Interruption Points**: Any node in your Workflow can request an interruption by specifying the data it want to present to the human. This could be a simple yes/no decision, an alert, or any structured data.

**State Preservation**: When an interruption happens, Neuron automatically saves the complete state of your Workflow. Your Workflow essentially goes to sleep, waiting for human input.

**Resume**: Once a human responde to the interruption request, the Workflow wakes up exactly from the node it left off. No data is lost, no context is forgotten.

**External Feedback Integration**: The edited interruption request is injected into the interrupted node to be continue its execution receiving the human feedback.

### Video Introduction

We know that Interruption flow is a quite advanced feature. Even with all the documentation below it may not be easy to grasp all aspects of this architecture. We're happy to link you below to an introductory video made by our community member [Amitav Roy](https://www.linkedin.com/in/royamitav/).

It might give you some additional information that, combined with the documentation, can help you understand how to implement your use cases.

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

### How it works

When you call for an interruption, the Workflow doesn't simply stop, it preserves its entire state, and waits for guidance before proceeding. This allows you to creates a hybrid intelligence system where AI handles the computational heavy lifting while humans contribute to strategic oversight, and decision-making.

The simplest way to ask for an interruption is calling the `interrupt()` method inside a node, providing an interruption request. Here is an example using the built-in `ApprovalRequest`:

```php
<?php

namespace App\Neuron;

use NeuronAI\Workflow\Events\Event;
use NeuronAI\Workflow\Interrupt\Action;
use NeuronAI\Workflow\Interrupt\ApprovalRequest;
use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Interrupt the workflow and wait for the feedback.
        $humanResponse = $this->interrupt(
            new ApprovalRequest(
                message: 'Should I continue?'
                actions: [
                    new Action('delete_file', 'Delete File', 'Delete /var/log/old.txt'),
                ],
            )
        );
    
        $action = $humanResponse->getAction('delete_file');
    
        if ($action->isApproved()) {
            $state->set('is_sufficient', true);
            $state->set('user_feedback', $action->feedback);
            return new OutputEvent();
        }
        
        $state->set('is_sufficient', false);
        return new InputEvent();
    }
}
```

You can eventually implement your custom interruption request to pass the information you need for the human interaction. You will be able to catch this data later, outside of the workflow so you can inform the user for feedback.

When the Workflow will be resumed it will restart from the same node it was interrupted, and the `$feedback` variable will receive the human's response data.

The `InterruptRequest` follows a **request-response pattern** where:

1. **Request Phase**: A workflow node identifies actions requiring human approval and creates an `InterruptRequest` containing details of those actions
2. **Pause Phase**: The workflow throws a `WorkflowInterrupt` exception, preserving the entire execution context
3. **Decision Phase**: The application presents actions to users, who approve, reject, or edit each action
4. **Resume Phase**: The workflow resumes with user decisions, continuing execution based on the feedback

This design ensures workflow can safely pause at any point, persist its state, and resume exactly where it left off, even across different sessions.

### Custom Interruption Request

The `InterruptRequest` is the central component of Neuron's human-in-the-loop (HITL) pattern, designed to pause workflow execution and request human approval or input for specific actions. It provides a structured, type-safe approach to building interactive AI workflows.

You can create your own implementation and feed it into the interrupt method.

```php
class ContentReviewInterrupt extends InterruptRequest
{
    public function __construct(
        protected string $message,
        protected string $content
    ) {
        parent::__construct($message)
    }
    
    public function getContent(): string
    {
        return $this->content;
    }
    
    public function jsonSerialize(): array
    {
        return [
            'message' => $this->message,
            'content' => $this->content,
        ];
    }
    
    public static function fromArray(array $data)
    {
        return new static($data['message'], $data['content']);
    }
}
```

Use it for your interrutpion use case:

```php
class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Generate an article
        $response = ContentCreatorAgent::make()
            ->chat(new UserMessage($event->prompt))
            ->getMessage();
    
        // Interrupt the workflow and wait for the feedback.
        $reviewRequest = $this->interrupt(
            new ContentReviewInterrupt(
                message: 'This is the new article. Review the content before saving it to the database.'
                $response->getContent()
            )
        );
        
        // Save the content of the updated interrupt request
        $state->set('content', $reviewRequest->getContent());
        
        return new InputEvent();
    }
}
```

### Catching the interruption

To be able to interrupt and resume a Workflow (also Agent and RAG) you need to provide the persistence layer when creating the Workflow instance:

```php
$workflow = new WorkflowAgent(new FilePersistence(__DIR__));
```

When a node call for an interruption the Workflow fires a special type of exception represented by the **`WorkflowInterrupt`** class. You can catch this exception to manage the interruption request.

```php
$workflow = new WorkflowAgent(
    new FilePersistence(__DIR__),
);

try {
    return $workflow->init()->run();
} catch (WorkflowInterrupt $interrupt) {
    $request = $interrupt->getRequest();
    $workflowId = $interrupt->getWorkflowId();
    
    /*
    * You can store the request as a json object
    * along with the resume token, and ask the user for a feedback.
    */
    $pdo->prepare("INSERT INTO interruption_requests (resume_token, request) VALUES (?, ?)");
    $pdo->execute([
        $workflowId,
        json_encode($request),
    ]);
}
```

Use the information in the `$request` object to guide the human in providing a feedback. Once you finally have the user's feedback you can resume the workflow passing the interruption request to the `init()` method. Remeber to use the same `workflowId` you got during interruption.

```php
$workflow = new WorkflowAgent(
    new FilePersistence(__DIR__),
    $workflowId // <- Use the same ID you got during interrutpion
);

$request = ContentReviewInterrupt::fromArray($data);

// Resume the Workflow passing the processed request as the feedback
$result = $workflow->init($request)->run();

// Get the final answer
echo $result->get('content');
```

You can take a look at the script below as an example of this process:

{% @github-files/github-code-block url="<https://github.com/inspector-apm/neuron-ai/blob/main/examples/workflow/workflow-interrupt.php>" %}

### Checkpointing

When the Workflow is resumed it restarts the execution from the node where it was interrupted. The node will be re-executed entirely including the code present before the interruption.

If you need to call for an interruption not at the beginning of the node, but after performing other operations, you can use checkpoints to save the result of previous statements to be used when the node is resumed. Here is an example:

```php
<?php

namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // The result of this code block is saved and returned when the workflow is resumed.
        $sentiment = $this->checkpoint('agent-1', function () {
            return MyAgent::make()->structured(
                new UserMessage(...),
                SentimentResult::class
            );
        });
        
        // Interrupt the workflow and wait for the feedback.
        if ($sentiment->isNegative()) {
            $feedback = $this->interrupt(
                new ApprovalRequest(
                    message: 'Should I continue?'
                    actions: [
                        new Action('review_id', 'Answer review', $sentiment->content),
                    ],
                )
            );
            
            if ($feedback->getAction('review_id')->isApproved()) {
                $state->set('is_sufficient', true);
                $state->set('user_feedback', $feedback->getAction('review_id')->feedback);
                return new OutputEvent();
            }
        }
        
        $state->set('is_sufficient', false);
        return new InputEvent();
    }
}
```

The checkpoint method accepts two arguments:

* The **name** of the checkpoint must be unique in the node;
* A **Closure** to wrap the code whose result you want to save.

When the node is executed, the checkpoint method saves the result of the Closure in case of an interruption. When the node is executed again after the interruption, it can reach the interruption point with the exact same state of the previous run to get the external feedback.

### Consume The Interruption Feedback

You can also consume the external feedback somewhere in your code other than where you call the `interrupt()` method.

The `consumeResumeRequest()` method allows you get the value of the external feedback or null if the node is simply running and not awakening:

```php
<?php

namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Ask for the final resume request
        $feedback = $this->consumeResumeRequest();
    
        // If the request has not thare yet jump to the interruption
        if ($feedback !== null && $feedback->getAction('review_id')->isApproved()) {
            $state->set('is_sufficient', true);
            $state->set('user_feedback', $feedback->getAction('review_id')->feedback);
            return new OutputEvent();
        }
        
        $this->interrupt(
            new ApprovalRequest(
                message: 'Should I continue?'
                actions: [
                    new Action('review_id', 'Answer review', $state->get('review')),
                ],
            )
        );
        
        $state->set('is_sufficient', false);
        return new InputEvent();
    }
}
```

This allows you to apply condition at the beginning of the node based on the given feedback.

### Conditional Interruption

You can also use `interruptIf()` as an helper to evaluate a conditional interruption:

```php
<?php

namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\WorkflowState;

class InterruptionNode extends Node
{
    public function __invoke(InputEvent $event, WorkflowState $state): OutputEvent
    {
        // Conditional interruption
        $this->interruptIf(
            $state->get('is_sufficient') == true, 
            new ApprovalRequest(
                message: 'Should I continue?'
                actions: [
                    new Action('review_id', 'Answer review', $state->get('review')),
                ],
            )
        );
        
        // Or use a callback to evaluate the condition
        $this->interruptIf(
            fn() => $state->get('is_sufficient', false), 
            new ApprovalRequest(
                message: 'Should I continue?'
                actions: [
                    new Action('review_id', 'Answer review', $state->get('review')),
                ],
            )
        );
        
        return new InputEvent();
    }
}
```

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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


# Persistence

Persist the Workflow State across executions.

When we talk about persistence in Neuron, we're talking about the system's ability to capture and preserve the complete state of a running workflow at any moment. This includes:

* **All variables and their current values**
* **The exact execution position** – which node is active, which have completed, which are waiting
* **Context and metadata** – timestamps, user information, decision history
* **Error states and retry counters** – so failures can be handled gracefully

Think of it like a sophisticated "save game" feature, but for business processes. At any point, when an interruption is asked from a node, Neuron create a snapshot of your workflow's state and store it in the persistence layer. Later – whether that's seconds, hours, or weeks – the workflow can be restored to exactly that moment and continue as if nothing happened.

As usual in Neuron the Workflow persistence layer is built on top of a common interface so it's extensible and interchangeable. Below the supported persistence layer.

### When to use Persistence

Persistence comes into play when you intend to use interruption (e.g. [Tool Approval](/agent/middleware#tool-approval-human-in-the-loop)).

### InMemoryPersistence

It keep data in memory only for the current execution cycle.

```php
use NeuronAI\Workflow\Persistence\InMemoryPersistence;

$workflow = new WorkflowAgent(
    new InMemoryPersistence()
);
```

### FilePersistence

It will store the Workflow data and state into a local file.

```php
use NeuronAI\Workflow\Persistence\FilePersistence;

$workflow = new WorkflowAgent(
    new FilePersistence(__DIR__), 
);
```

### Database

To persist the workflow interruption in the database you need to pass a `PDO` instance. If you are working on top of a framework you can easily get it from the ORM in the same way of the [SQLChatHistory](/agent/chat-history-and-memory#sqlchathistory).

```php
use NeuronAI\Workflow\Persistence\DatabasePersistence;

$workflow = new WorkflowAgent(
    new DatabasePersistence(
        pdo: new \PDO(...),
        table: 'workflow_interrupts'
    ), 
);
```

Here are the SQL scripts to create the table:

{% tabs %}
{% tab title="MySQL/MariaDB" %}

```sql
CREATE TABLE IF NOT EXISTS workflow_interrupts (
    workflow_id VARCHAR(255) PRIMARY KEY,
    interrupt LONGBLOB NOT NULL,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    
    INDEX idx_workflow_id (workflow_id),
    INDEX idx_updated_at (updated_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

{% endtab %}

{% tab title="PostgreSQL" %}

```sql
CREATE TABLE workflow_interrupts (
    workflow_id VARCHAR(255) PRIMARY KEY,
    interrupt BYTEA NOT NULL,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL
);

CREATE INDEX idx_workflow_id ON workflow_interrupts(workflow_id);
CREATE INDEX idx_updated_at ON workflow_interrupts(updated_at);
```

{% endtab %}

{% tab title="SQLite" %}

```sql
CREATE TABLE workflow_interrupts (
    workflow_id TEXT PRIMARY KEY,
    interrupt BLOB NOT NULL,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

CREATE INDEX idx_workflow_id ON workflow_interrupts(workflow_id);
CREATE INDEX idx_updated_at ON workflow_interrupts(updated_at);
```

{% endtab %}
{% endtabs %}

### Eloquent

You should create your own Eloquent model and pass the class string as the constructor argument. The model can have custom relations, scopes, attributes, etc. but the basic structure must be based on this migration script:

```bash
php artisan make:migration create_workflow_interrupts_table --create=workflow_interrupts
```

```php
Schema::create('workflow_interrupts', function (Blueprint $table) {
    $table->id();
    $table->string('workflow_id')->unique();
    $table->longText('interrupt')->charset('binary');
    $table->timestamps();
});
```

#### WorkflowInterrupt model

This is the minimal required structure:

```php
class WorkflowInterrupt extends Model
{    
    protected $fillable = ['workflow_id', 'interrupt'];
}
```

Use with Workflow:

```php
use App\Models\WorkflowInterrupt;
use NeuronAI\Workflow\Persistence\EloquentPersistence;

// Creating a workflow
$workflow = WorkflowAgent(
    persistence: new EloquentPersistence(WorkflowInterrupt::class)
);
```


# Streaming

Stream real -time updates during workflow execution

Workflows can be complex, they are designed to handle complex, branching, itarable logic, which means they can take time to fully execute. To provide your user with a good experience, you may want to provide an indication of progress by streaming events as they occur. Workflows have built-in support for this directly from inside the nodes.

### Emit events from nodes

Let's set up a new event to handle streaming our progress as we go:

```php
namespace App\Neuron;

class ProgressEvent implements Event 
{
    public function __construct(protected string $msg){}
}
```

We'll take our example MyWorkflow with multiple nodes from the previous tutorial and modify the nodes to stream upadtes instead of echoing output directly.

{% hint style="warning" %}
**Notice**: To stream events from node you need to add `\Generator` as additional return type of the `__invoke` method.
{% endhint %}

```php
namespace App\Neuron;

use NeuronAI\Workflow\Node;
use NeuronAI\Workflow\StartEvent;
use NeuronAI\Workflow\StopEvent;

class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): \Generator|FirstEvent
    {
        yield new ProgressEvent("Handling StartEvent");
        
        return new FirstEvent("InitialNode complete");
    }
}

class NodeOne extends Node
{
    public function __invoke(FirstEvent $event, WorkflowState $state): \Generator|SecondEvent
    {
        yield new ProgressEvent($event->firstMsg);
        
        return new SecondEvent("NodeOne complete");
    }
}

class NodeTwo extends Node
{
    public function __invoke(SecondEvent$event, WorkflowState $state): \Generator|StopEvent
    {
        yield new ProgressEvent($event->secondMsg);
        
        yield new ProgressEvent("NodeTwo complete");
        
        $state->set('message', 'Streaming end');
        
        return new StopEvent();
    }
}
```

To actually get this output, we need to start the workflow and listen for the events, like this:

```php
$handler = Workflow::make()
    ->addNodes([
        new InitialNode(),
        new NodeOne(),
        new NodeTwo(),
    ])
    ->init();

$stream = $handler->events();

foreach ($stream as $event) {
    if ($event instanceof ProgressEvent) {
        echo "\n- ".$event->message;
    }
}

$finalState = $stream->getResult();

// It will print "Streaming end"
echo "\n- ".$finalState->get('message');
```

The full output will be:

```
- Handling StartEvent
- InitialNode complete
- NodeOne complete
- NodeTwo complete
- Streaming end
```

### Stream Agent Output

Running Agents inside nodes is one of the most common use case working with workflow. You may be interested in directly stream an internal agent output to the client to give real time feedback of the underlying generation. You can do it by simply streaming the agent's output from within the node.

```php
class InitialNode extends Node
{
    public function __invoke(StartEvent $event, WorkflowState $state): \Generator|FirstEvent
    {
        // Run an agent with streaming
        yield from Agent::make()
            ->stream(new UserMessage($state->get('prompt')))
            ->events();
        
        return new FirstEvent("InitialNode complete");
    }
}
```

To get this output you can listen for workflow events as usual:

```php
$handler = MyWorkflow::make()->init();

foreach ($handler->events() as $event) {
    echo match($event::class) {
        TextChunk::class => "\n- ".$event->content,
        ...
    }
}
```

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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


# Middleware

Control and customize agent execution at every step

### What is a Middleware

Middleware provides a way to hook the workflow execution and therefore also Agent and RAG, since they too are workflows.

The core Workflow execution involves calling nodes based on the events returned by other nodes. Middleware exposes hooks to step inside `before` and `after` the execution of nodes:

<figure><img src="/files/WLjXxevp8EFF94o0wv4N" alt=""><figcaption></figcaption></figure>

### What can middleware do?

<table data-view="cards"><thead><tr><th></th><th></th></tr></thead><tbody><tr><td><strong>Control</strong></td><td>Transform prompts, tool selection, and output formatting.</td></tr><tr><td><strong>Guardrails</strong></td><td>Add retries, rate limits, implement guardrails, prompt rejection.</td></tr><tr><td><strong>Monitor</strong></td><td>Track agent behavior with logging, analytics, and debugging.</td></tr></tbody></table>

### Creating Middleware

You can use the command below to create a middleware class:

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

```bash
vendor/bin/neuron make:middleware CustomMiddleware
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:middleware CustomMiddleware
```

{% endtab %}
{% endtabs %}

A new class will be created in your project with the default middleware structure:

```php
<?php

namespace App\Neuron\Middleware;

class CustomMiddleware implements WorkflowMiddleware
{
    /**
     * Execute before the node runs.
     */
    public function before(NodeInterface $node, Event $event, WorkflowState $state): void
    {
        // ...
    }
    
    /**
     * Execute after the node runs.
     */
    public function after(NodeInterface $node, Event $result, WorkflowState $state): void
    {
        // ...
    }
}
```

### Registering Middleware

If you would like to assign middleware to specific nodes, you may override the `middleware` method when defining the workflow:

```php
class MyWorkflow extends Workflow
{
    /**
     * Define the nodes middleware.
     */
    protected function middleware(): array
    {
        return [
            NodeOne::class => new CustomMiddleware(),
        ];
    }
}
```

You can also assign multiple middleware to a node, defining an array:

```php
class MyWorkflow extends Workflow
{
    /**
     * Define the nodes middleware.
     */
    protected function middleware(): array
    {
        return [
            NodeOne::class => [
                new CustomMiddleware(),
                new AnotherMiddleware(),
            ]
        ];
    }
}
```

### Global Middleware

If you want middleware to run before and after every node, you may append it to the global middleware stack in the workflow:

```php
class MyWorkflow extends Workflow
{
    /**
     * Define the global middleware.
     */
    protected function globalMiddleware(): array
    {
        return [
            new CustomMiddleware(),
        ];
    }
}
```

### Examples

Neuron provides prebuilt middleware for common use cases, like tool approval, or context summarization. You can check them out in the Agent section:

{% content-ref url="/pages/bFnrksGXQFcgecFqBMBF" %}
[Middleware](/agent/middleware)
{% endcontent-ref %}


# Tips & Tricks

Overcome bottlenecks, and learn about the Workflow features through real code examples

### Too many nodes?

When you work with many nodes, loops, or branches, nodes are always registered in the workflow as a flat list. Having too many nodes can make it difficult to visually follow the execution flow and focus on a certain part of the workflow for changes or new development.

Instead of a flat 20-node array, break it into logical sections:

```php
 class MyWorkflow extends Workflow
 {
     ... 
    protected function nodes(): array
    {
        return [
            ...$this->classificationProcess(),
            ...$this->analysis(),
            ...$this->postProcessing(),
        ];
    }
    
    /**
     * Initial classification process
     */
    protected function classificationProcess(): array
    {
        return [
            // Nodes here...
        ];
    }
}
```

The spread operator (...) keeps the runtime behavior identical. It's still a flat array. But now the\
`nodes()` method reads like a table of contents, and each group is self-documenting.

## Example Projects

### AIForm - Conversational Data Collection

AIForm is a component for collecting structured data through multi-turn natural language conversations. It uses an AI agent to progressively gather information defined by a structured output class, validating each piece of data along the way.

The form maintains conversation history, tracks collected fields, missing fields, and validation errors across multiple turns.

<a href="https://github.com/neuron-core/ai-form" class="button secondary" data-icon="github">Check out the GitHub repository</a>

### Deep Research Agent

This project is inspired by Open Deep Research, which uses LangGraph for implementation. Other implementations exist also for llamaindex, and others. Our version leverages Neuron to create a powerful, modular workflow for research and analysis.

Neuron Open Deep Research provides a structured approach to generating comprehensive research reports on any topic using large language models, with a focus on modularity, extensibility, and real-time results.

#### Architecture

**DeepResearchAgent**: Orchestrates the overall report generation process

* **Planning**: Creates the structure of the report
* **GenerateSectionContent**: Generates content for each section using search results
* **Format**: Compiles the final report

**SearchWorkflow**: Handles search operations as a nested workflow

* **GenerateQueries**: Creates search queries based on section topics
* **SearchTheWeb**: Executes parallel searches and processes results

<a href="https://github.com/neuron-core/deep-research-agent" class="button secondary" data-icon="github">Check out the GitHub repository</a>

### Travel Planner Agent

This project demonstrates how to create a tour planner using Neuron PHP framework for agentic applications.

Stack Used:

* Neuron Workflow for multi-agent orchestration.
* [SerpAPI](https://serpapi.com/) for finding hotels, flights and places to visit comprehensive research reports on any topic using large language models, with a focus on modularity, extensibility, and real-time results.

#### Architecture

**TravelPlannerAgent**: Orchestrates the overall itinerary generation process

#### Nodes

* **Receptionist**: Collect all the information from the user
* **Delegator**: Generates single reports for flights, hotels, and places to visit
  * *Flights*
  * *Hotels*
  * *Places*
* **GenerateItinerary**: Generates the final report

<a href="https://github.com/neuron-core/travel-planner-agent" class="button secondary" data-icon="github">Check out the GitHub Repository</a>

### Laravel Travel Agent

This project demonstrates how to integrate multi-agent workflows in a Laravel application using Neuron PHP AI framework.

Stack Used:

* [Laravel](https://laravel.com/) and [Livewire](https://livewire.laravel.com/) for the application.
* [Neuron Workflow](https://docs.neuron-ai.dev/workflow/getting-started) for multi-agent orchestration.
* [SerpAPI](https://serpapi.com/) for finding hotels, flights and places to visit comprehensive research reports on any topic using large language models, with a focus on modularity, extensibility, and real-time results.

<figure><img src="/files/2tRw8eZPhkZxW3Ms4i2i" alt=""><figcaption></figcaption></figure>

#### How to use this project

Download the project on your machine and open your terminal in the project directory. First, install the composer dependencies:

```bash
composer install

npm run build

php artisan migrate
```

Create a `.env` file in your project root (see `.env.example` for a template), and provides the API keys based on the service you want to connect with.

```
# At least one required
ANTHROPIC_API_KEY=
GEMINI_API_KEY=
OPENAI_API_KEY=

#Required
SERPAPI_KEY=

# Optional
INSPECTOR_INGESTION_KEY=
INSPECTOR_TRANSPORT=sync
```

Open the project in your browser, register an account, and start planning your trip.


# Introduction

Learn what Neuron is and what you can do with it.

### What is Neuron

Neuron is a PHP framework for developing agentic applications. By handling the heavy lifting of orchestration, data loading, and debugging, Neuron clears the path for you to focus on the creative soul of your project. From the first line of code to a fully orchestrated multi-agent system, you have the freedom to build AI entities that think and act exactly how you envision them.

We provide tools for the entire agentic application development lifecycle, from LLM interfaces, to data loading, to multi-agent orchestration, to monitoring and debugging. In addition, we provide [tutorials and other educational content](/neuron-v4/overview/fast-learning-by-video) to help you get started using AI Agents in your projects.

<figure><img src="/files/X4g0nU5EGJ0bn1dbtQcy" alt=""><figcaption><p>Neuron architecture</p></figcaption></figure>

### Getting Started In 3 Steps

**1) Install** Neuron in you project:

```shellscript
composer require neuron-core/neuron-ai
```

**2) Create** an agent extending the `Agent` class:

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
}
```

**3) Talk** with the agent:

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

$message = MyAgent::make()
    ->chat(new UserMessage("Hi, who are you?"))
    ->getMessage();

echo $message->getContent();
// I'm a friendly AI Agent built with Neuron AI framework, how can I help you today?
```

### Demo with Laravel

Neuron offers a well defined encapsulation pattern, allowing you to work on your AI components in a dedicated namespace. You can enjoy the exact same experience of the other ecosystem packages you already love, like Filament, Nova, etc.

<a href="https://www.youtube.com/watch?v=oSA1bP_j41w" class="button primary" data-icon="youtube">Watch the demo</a>

### Demo with Symfony

All Neuron components belong to its own interface, so you can easily define dependencies and automate objects creation using the Symfony service container. Watch how it works in a real project.

<a href="https://www.youtube.com/watch?v=JWRlcaGnsXw" class="button primary" data-icon="youtube">Symfony & Neuron</a>

### Support For Multiple Providers

Neuron uses a common interface for large language models (`AIProviderInterface`) as well as for the other components, such as [embedding](/neuron-v4/rag/embeddings-provider), [vector stores](/neuron-v4/rag/vector-store), [toolkits](/neuron-v4/agent/tools#toolkits-composable-agent-capabilities), etc. The modular architecture allows you to swap components as needed, whether you're changing LLM provider, adjusting memory backends, or scaling across multiple servers.

Here are a couple of examples:

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

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Ollama" %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Ollama\Ollama;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Ollama(
            url: 'OLLAMA_URL',
            model: 'OLLAMA_MODEL',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="OpenAI" %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\OpenAI\OpenAI;

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

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Gemini" %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Gemini;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Gemini(
            key: 'GEMINI_API_KEY',
            model: 'GEMINI_MODEL',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}

{% tab title="Mistral" %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\UserMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Gemini\Mistral;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        return new Mistral(
            key: 'MISTRAL_API_KEY',
            model: 'MISTRAL_MODEL',
        );
    }
}

$message = MyAgent::make()
    ->chat(new UserMessage("Hi!"))
    ->getMessage();

echo $message->getContent();
// Hi, how can I help you today?
```

{% endtab %}
{% endtabs %}

Check out all the supported providers in the [AI Provider](/neuron-v4/providers/ai-provider) section.

### Video Tutorials

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

More resources here: [Video Tutorials](/neuron-v4/overview/fast-learning-by-video#video)

### Why Neuron

Your next application will be agentic. A growing share of new software is no longer a web application with AI features added along the way, but an application born agentic, where the agent is the architecture itself, driving how the system reasons, acts, and talks to the user interface. Building this kind of application requires a specific set of foundations: event-driven workflows with checkpointing, human-in-the-loop, interruption, multi-agent orchestration, streaming, and agentic UI protocols like AG-UI and the Vercel AI SDK protocol, MCP, and asynchronous execution.

In the PHP ecosystem, this set of foundations exists in one place. Each one is a chapter of this documentation: [Workflow](/neuron-v4/workflow/getting-started), [Human in the loop](/neuron-v4/agent/tool-approval), [Streaming & UI protocols](/neuron-v4/agent/streaming#stream-adapters), [MCP](/neuron-v4/agent/mcp-connector), [Async](/neuron-v4/agent/async). You can compare it with any other option available to a PHP developer, and the comparison is the answer.

There is also no second framework waiting for you when the project grows. The same Workflow that runs your first agent in the getting started guide runs a multi-agent system with state, loops, and human approvals in production. What you learn on day one is what you ship in future projects.

### A Vertical & Independent Ecosystem

Neuron is also the only vertical ecosystem for agentic applications development in PHP. Around the framework there is a registry of extensions, tools, and technologies designed specifically for agentic applications, and a growing number of companies building on the same architecture instead of assembling their own from scattered parts.&#x20;

For a software house, this is a place to be recognized as a specialist rather than one more team claiming AI experience. For a company that needs an agentic foundation it can commit to for years, it means standardizing on an architecture whose whole direction is this space, not a general-purpose library where agents are a side feature.

## Resources

### [E-Book - "Start With AI Agents In PHP"](https://www.amazon.it/dp/B0F1YX8KJB)

The gap between modern agentic technologies and traditional PHP development has been widening in recent years. While Python developers enjoy a wealth of libraries and frameworks to create AI Agents, PHP developers have often been left wondering how they can participate in this technological revolution without completely retooling their skillsets or rebuilding their applications from scratch.

Neuron changes all that.

This book serves as both an introduction to AI Agents concepts for developers and a comprehensive guide to Neuron framework.

<a href="https://www.amazon.com/dp/B0F1YX8KJB" class="button secondary" data-icon="amazon">Get on Amazon</a>&#x20;

<a href="https://play.google.com/store/books/details?pcampaignid=books_read_action&#x26;id=agJPEQAAQBAJ&#x26;pli=1" class="button secondary" data-icon="google">Get on GooglePlay</a>

### [Newsletter](https://neuron-ai.dev)

Register to the Neuron internal [newsletter](https://neuron-ai.dev/) to get informative papers, articles, and best practices on how to start with AI development in PHP.

You will learn how to approach AI systems in the right way, understand the most important technical concepts behind LLMs, and how to start implementing your AI solutions into your PHP application with the Neuron AI framework.

### [Forum](https://github.com/inspector-apm/neuron-ai/discussions)

We’re using [Discussions](https://github.com/inspector-apm/neuron-ai/discussions) as a place to connect with PHP developers working on Neuron to create their Agentic applications. We hope that you:

* Ask questions you’re wondering about.
* Share ideas.
* Engage with other community members.
* Welcome others and are open-minded.

### [**Inspector.dev**](https://inspector.dev)

Neuron is part of the Inspector ecosystem as a trustable platform to create reliable and scalable AI driven solutions.&#x20;

Trace and evaluate your agents execution flow to help you maintain production grade implementations with confidence. Check out the [**monitoring integrations**](/neuron-v4/agent/observability).

## Keep In Touch

* Website & Newsletter: [https://neuron-ai.dev](https://neuron-ai.dev/)
* Repository: [https://github.com/neuron-code/neuron-ai](https://github.com/inspector-apm/neuron-ai)
* Inspector: <https://inspector.dev>
* E-Book: <https://www.amazon.it/dp/B0F1YX8KJB>
* Linkedin: <https://www.linkedin.com/company/neuron-ai-php-framework>
* X: <https://x.com/neuronai_php>
* Instagram: <https://www.instagram.com/neuronai_php_adk/>


# Installation

Step by step instructions on how to install Neuron in your application and create an Agent.

### Requirements

* PHP: ^8.1

### Install

Run the command below to install the latest version:

```bash
composer require neuron-core/neuron-ai
```

### Create an Agent

You can easily create your first agent with the Neuron CLI:

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

```bash
./vendor/bin/neuron make:agent App\\Neuron\\MyAgent
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:agent App\Neuron\MyAgent
```

{% endtab %}
{% endtabs %}

```php
namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Agent\SystemPrompt;
use NeuronAI\Providers\Anthropic\Anthropic;

class MyAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }

    public function instructions(): string
    {
        return (string) new SystemPrompt(
            background: ["You are a friendly AI Agent created with Neuron framework."],
        );
    }
}
```

### Talk to the Agent

Send a prompt to the agent to get a response from the underlying LLM:

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

$message = MyAgent::make()
    ->chat(new UserMessage("Hi, who are you?"))
    ->getMessage();

echo $message->getContent();
// I'm a friendly AI Agent built with Neuron, how can I help you today?
```

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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

### Video Tutorial On A Laravel Application

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


# Upgrade

## Upgrade to v4 from v3

In this new major version the public APIs of Neuron components weren't changed dramatically (we minimized the impact as much as possible). We've focused on improving the most important component on which the entire framework is based, Workflow. Since it's the foundation of the entire architecture, the changes implemented in this new version may impact your code, especially if you use advanced patterns like interrupts and persistence.

We also took advantage of this release to fix other critical issues emerged in the v3 like the Tool Approval flow in the Agent, and other design improvements to have more freedom to evolve the framework with less breaking changes in the future.

We continue to work to provide the best possible developer experience, to help you create successful AI products in PHP.

{% hint style="danger" %}

### Agentic Upgrade (recommended)

We documented the entire upgrade process in a dedicated directory <https://github.com/neuron-core/neuron-ai/tree/4.x/upgrade>. You can point your coding agent to this directory and it will automatically receive the instructions to migrate your code step by step.
{% endhint %}

## Updating Dependencies

You should update the following dependencies in your application's `composer.json` file:

```json
{
    "repositories": [
        {
            "type": "vcs",
            "url": "https://github.com/neuron-core/neuron-ai"
        }
    ],
    "require": {
        ...,
        "neuron-core/neuron-ai": "4.x-dev"
    },
}
```

The `inspector-php` package was removed from default dependencies. So you have to install it in your application if you want to connect your agent to the [Inspector](https://inspector.dev/) monitoring dashboard:

```shellscript
composer require inspector-apm/inspector-php
```

## High Impact Changes

### Tool becomes abstract

The `Tool` class is no longer a concrete class and can no longer be used directly. Its design is now intended to be extendable, allowing you to implement your own tools with less code and more flexibility.

We also removed the constructor from the abstract class so you can specify tool name and description as normal class properties instead of calling the parent constructor. You are free to use a class constructor only if you want to pass external dependencies to the tool:

```php
class MyTool extends Tool
{
    protected string $name = 'my_tool';
    
    protected ?string $description = 'What the tool does.';
    
    public function __construct(protected string $apiKey){}
    
    public function __invoke()
    {
        ...
    }
}
```

### Remove WorkflowHandler

The Workflow component was subject of an important refactoring in order to simplify its usage and public APIs. Working with Workflow in the previous version, you were need to call the `init()` method to get the `WorkflowHandler` instance and than call `run()` or `events()` on the handler to finally execute the workflow:

```php
$handler = MyWorkflow::make()->init();

// One shot run
$finalState = $handler->run();

// Stream events
foreach($handler->events() as $chunk) {
    // ...
}
$finalState = $handler->getResult();
```

Following a drastic simplification of the workflow execution logic, the handler is no longer necessary and it is possible to invoke the two methods `run()` and `events()` directly in the workflow.

```php
// One shot run
$finalState = MyWorkflow::make()->run();

// Stream events
$generator = MyWorkflow::make()->events();
foreach($generator as $chunk) {
    // ...
}
$finalState = $generator->getResult();
```

### Workflow Interrupt/Resume

The architecture of the workflow execution and its interruption capabilities was redisigned to make it easier to manage interruption and tool approval, but also open the doors for the implementation of durable, crash proof, agentic workflows.

#### Remove WorkflowInterrupt exception

In case of interruption the Workflow doesn't throw the special `WorkflowException` to inform the caller script about the interruption. It just return an "interrupted" state:

```php
$state = $workflow->run();

if ($state->isInterrupted()) {
    // Use the information in the request
    $request = $state->getInterruptRequest();
    // The resume token is auto-generated and available from the workflow instance
    $workflowId = $workflow->getWorkflowId();
}
```

No more try/catch block.

This change the [Tool Approval](/neuron-v4/agent/tool-approval) flow. Check out the documentation in case you are using this middleware in your agents.

#### Resume Payload as plain array

The interruption request you propagate from the node is now only a signal to carry information from the node to the outside caller script. To resume the workflow you no longer need to pass the request back to the workflow. The resume payload is now just a simple array:

```php
// Example of a node calling interrupt()
class InterruptableNode extends Node
{
    public function __invoke(FirstEvent $event, WorkflowState $state): NextEvent
    {
        $payload = $this->interrupt(new ApprovalRequest('human input needed'));
        $state->set('received_feedback', $payload);
        return new NextEvent();
    }
}

// Define the inbound payload — it will be returned by the interrupt() method
$payload = ['action_id' => 'approve'];

$finalState = $workflow->resume($payload);
```

### Agent Instructions

Agent instructions must be an instance of a `SystemMessage`. You can just pass the string to the constructor to make it compatible with this new version:

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

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        ...
    }
    
    protected function instructions(): SystemMessage
    {
        return new SystemMessage(<<<TEXT
            You are an AI Agent specialized in writing YouTube video summaries.
            Get the url of a YouTube video, or ask the user to provide one.
            Use the tools you have available to retrieve the transcription of the video.
            Write a summary in a paragraph without using lists. Use just fluent text.
        TEXT);
    }
}
```

### Tool Approval

The tool approval flow was entirely rewritten. The Agent class now manages the entire process. You just need to take care of rendering the UI so users can decide whether to approve or reject a tool call.

The status of tools requiring approval is stored into the chat history within the last `ToolCallMessage`. This allows you to design the UI to just render the messages in the chat history, and when it meets a `ToolCallMessage` you can check the approval status of the tools to show the Approve/Deny actions, or the normal tool call already happened.

<a href="/pages/SzeKvZMM3OYeEQzCgw7A" class="button primary" data-icon="arrow-right-long">Tool Approval</a>

#### New Tool `requiresApproval()` method

We introduced the `requiresApproval()` method on the Tool class to determine whether approval is needed based on the tool call's arguments:

```php
class MyTool extends Tool
{
    ...,
    
    public function requiresApproval(array $inputs): bool
    {
        return $inputs['amount'] > 100;
    }
}
```

To activate the tool approval flow you always need to attach the [ToolApproval](/neuron-v4/agent/tools#tool-approval) middleware to the Agent. Custom approval policy on the middleware have precedence over the one defined in the tool's `requiresApproval()` method.

### Database Schema For Workflow Persistence

Due to the changes in the workflow execution model, the database schema for persistence across interruptions has been changed to support the new features. Check out the dedicated section to get ready to run SQL queries to start with the new database format.

<a href="/pages/rM5zmOIVvcgRCLyvIY1S" class="button primary" data-icon="arrow-right-long">Persistence</a>

## Medium Impact

### Monitoring

The framework is transitioning to the [PSR-14 Event Dispatcher](https://www.php-fig.org/psr/psr-14/) interface, instead of the PHP native \SplObserver. We kept the existing interfaces in place, and also the `LogObserver` usgin adapters, but they are marked as `@deprecated`.

This will make it easier to integrate Neuron agents and agentic workflows in general with other existing frameworks and applications.

<a href="/pages/TjLfzzT2yuogGE2zejCa" class="button primary" data-icon="arrow-right-long">Monitoring</a>

### Providers return ProviderResponse

AI provider methods `chat()` and `stream()` now return `ProviderResponse` instead of `Message`. The `ProviderResponse` wraps the assistant message and provides access to the raw HTTP response body and headers.

**This only affects standalone provider usage.** When providers are used inside an Agent (via `chat()`, `stream()`, or `structured()` on the Agent itself), no changes are needed — the Agent handles the `ProviderResponse` internally.

You only need to refactor code that calls provider methods directly, such as in scripts, controllers, commands, or custom workflows.

```php
$response = $provider->chat(new UserMessage(...));

// Get the assistance Message
$response->message();

// Get the raw provider body
$response->body();

// Get the headers
$response->headers();
```

### Changes on AIProviderInterface

We removed `messageMapper()` and `toolPayloadMapper()` from the `AIProviderInterface`, and the new `getModel()` methos was introduced. If you have any custom provider implementation that directly use this interface, you need to adjust it properly.

```php
interface AIProviderInterface
{
    public function getModel(): string;

    public function systemPrompt(string|array|null $prompt): AIProviderInterface;

    public function setTools(array $tools): AIProviderInterface;

    public function chat(Message ...$messages): ProviderResponse;

    public function stream(Message ...$messages): Generator;

    public function structured(array|Message $messages, string $class, array $response_schema): ProviderResponse;

    public function setHttpClient(HttpClientInterface $client): AIProviderInterface;
}
```


# Video Tutorials

Position yourself in the AI Agent era with our extensive tutorials and technical insights into Neuron capabilities. Learn from practical examples and real-world use cases.

## Video

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

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

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

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

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

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

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

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

## Articles

### Agent Development

[PHP, the Dark Horse No One Saw Coming In AI Agents development](https://inspector.dev/php-the-dark-horse-no-one-saw-coming-in-ai-agents-development/)

[LangChain alternative for PHP developers](https://inspector.dev/langchain-alternative-for-php-developers/)

[System Prompt for AI Agents In PHP](https://inspector.dev/system-prompt-for-ai-agents-in-php/)

[AI Agents Memory And Context Window In PHP](https://inspector.dev/ai-agents-memory-and-context-window-in-php/)

[Create AI Agents In PHP Powered By Google Gemini LLMs](https://inspector.dev/create-ai-agents-in-php-powered-by-google-gemini-llms/)

### RAG (Retrieval Augmented Generation)

[How to Create a RAG Agent with Neuron ADK for PHP](https://inspector.dev/how-to-create-a-rag-agent-with-neuron-adk-for-php/)

[Vector Store & AI Agents – Beyond The Traditional Data Storage](https://inspector.dev/vector-store-ai-agents-beyond-the-traditional-data-storage/)

[Improve PHP AI Agents output quality with Rerankers](https://inspector.dev/improve-php-ai-agents-output-quality-with-rerankers/)

### Tools & Toolkits

[Introducing Toolkits: Composable AI Agent Capabilities In PHP](https://inspector.dev/introducing-toolkits-composable-ai-agent-capabilities-in-php/)

[Create A Data Analyst Agent In PHP – Neuron MySQL Toolkit](https://inspector.dev/mysql-ai-toolkit-bringing-intelligence-to-your-database-layer-in-php/)

[Introducing Web Search Capabilities For PHP AI Agents](https://inspector.dev/introducing-web-search-capabilities-for-php-ai-agents/)

[Introducing Vision Capabilities for PHP AI Agents](https://inspector.dev/introducing-vision-capabilities-for-php-ai-agents/)

[AI Agents in PHP with MCP (Model Context Protocol)](https://inspector.dev/ai-agents-in-php-with-mcp-model-context-protocol/)

### Workflow

[Introducing Neuron Workflow: The future of agentic PHP applications](https://inspector.dev/introducing-neuronai-workflow-the-future-of-agentic-php-applications/)

[Deep Research Agent Implementation](https://inspector.dev/multi-agent-systems-in-php-a-practical-deep-research-implementation/)

[Laravel Travel Agent](https://inspector.dev/building-multi-agent-systems-in-laravel-a-practical-demo/)

[Managing Human-in-the-Loop With Checkpoints](https://inspector.dev/managing-human-in-the-loop-with-checkpoints-neuron-workflow/)

## E-Book

The gap between modern agentic technologies and traditional PHP development has been widening in recent years. While Python developers enjoy a wealth of libraries and frameworks to create AI Agents, PHP developers have often been left wondering how they can participate in this technological revolution without completely retooling their skillsets or rebuilding their applications from scratch.

Neuron changes all that.

<figure><img src="/files/icOUu6fFXtKRtH0mEBG9" alt="" width="375"><figcaption></figcaption></figure>

As a PHP developer, you now stand at a unique intersection of technologies. For years, PHP has powered a substantial portion of the web. Now, with Neuron AI, you have the ability to infuse these web experiences with artificial intelligence, without leaving the language and ecosystem you know and love.

Neuron is the most advanced PHP framework to build AI driven applications. This book serves as both an introduction to AI Agents concepts for developers and a comprehensive guide to Neuron PHP agentic framework.&#x20;

Get it from [Amazon](https://www.amazon.com/dp/B0F1YX8KJB) or [Google Play](https://play.google.com/store/books/details?pcampaignid=books_read_action\&id=agJPEQAAQBAJ\&pli=1).

<a href="https://www.amazon.com/dp/B0F1YX8KJB" class="button secondary" data-icon="amazon">Amazon Books</a>&#x20;

<a href="https://play.google.com/store/books/details?pcampaignid=books_read_action&#x26;id=agJPEQAAQBAJ&#x26;pli=1" class="button secondary" data-icon="google">Google Play</a>


# AI-Assisted Development

Connect the documentation to coding agents for AI Assisted Development

When working with AI coding assistants like Claude Code, Opencode, Cursor, or other similar tools, you can reference the Neuron AI documentation to give the AI deep context about our components. This leads to more accurate code suggestions, better understanding of component APIs, and fewer hallucinations when generating Neuron code.

## Agent Skills

The [Agent Skills specification](https://agentskills.io/) is a standard for providing structured documentation to AI coding assistants. It helps AI tools understand your project's APIs, conventions, and best practices through a well-organized directory of markdown files.

Neuron publishes an Agent Skill that provides AI tools with comprehensive information about our components, including their APIs, usage patterns, interfaces, and more.

### Available Skills (recommended)

{% hint style="info" %}
Type **`/neuron-*`** in your terminal.
{% endhint %}

The Agent Skill is available in the Neuron AI vendor folder at:

```bash
vendor/neuron-core/neuron-ai/skills/
        └── neuron-agent/
            └── SKILL.md
        └── neuron-monitoring/
            └── SKILL.md
        └── neuron-evaluation/
            └── SKILL.md
        └── neuron-test/
            └── SKILL.md
        └── neuron-rag/
            └── SKILL.md
        └── neuron-structured-output/
            └── SKILL.md
        └── neuron-tool/
            └── SKILL.md
        └── neuron-tool-approval/
            └── SKILL.md
        └── neuron-workflow/
            └── SKILL.md

```

### How to install skills

How you reference the skill depends on which AI tool you're using.

#### **Claude**

If you're using [Claude Code](https://claude.ai/code), you can install the Neuron AI skills locally using the [skills CLI](https://skills.sh/):

```bash
npx skills add ./vendor/neuron-core/neuron-ai/skills
```

Once installed, the skill will be available to Claude Code automatically. The skilla are installed as a symlink, so it will automatically stay up to date when you update Neuron via composer.

#### Cursor  <a href="#cursor" id="cursor"></a>

In [Cursor](https://cursor.sh/), you can add the skill directory to your project's documentation sources via **Cursor Settings > Features > Docs**. Point it to the `vendor/neuron-core/neuron-ai/skills` .

#### Other AI Tools  <a href="#other-ai-tools" id="other-ai-tools"></a>

Most AI coding assistants that support the Agent Skills specification can use this skill. Check your tool's documentation for how to add custom skills or documentation sources.

## MCP Server

This documentation is also available and searchable as a Model Context Protocol (MCP) server. This allows AI assistants to access Neuron AI documentation content directly. The MCP server is available at: <https://docs.neuron-ai.dev/~gitbook/mcp>

### Claude Code

```
claude mcp add --transport http neuron-ai-doc https://docs.neuron-ai.dev/~gitbook/mcp
```

### VS Code

```json
"mcp": {
    "servers": {
        "neuron-ai-doc": {
            "type": "http",
            "url": "https://docs.neuron-ai.dev/~gitbook/mcp"
        }
    }
}
```

### Cursor

```json
{
  "mcpServers": {
    "neuron-ai-doc": {
        "url": "https://docs.neuron-ai.dev/~gitbook/mcp"
    }
  }
}
```

### Windsurf

```json
{
  "mcpServers": {
    "neuron-ai-doc": {
      "serverUrl": "https://docs.neuron-ai.dev/~gitbook/mcp"
    }
  }
}
```

### OpenCode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "neuron-ai-doc": {
      "type": "remote",
      "url": "https://docs.neuron-ai.dev/~gitbook/mcp",
      "enabled": true
    }
  }
}
```


# Agent

Easily implement LLM interactions with built-in memory and tool usage.

{% hint style="warning" %}

#### Coding Agent Skill

Use `/neuron-agent` to teach your coding agent how to implement an agent using Neuron AI components.

[AI-Assisted Development](/neuron-v4/overview/agentic-development)
{% endhint %}

### Introduction

You can create your agent by extending the `NeuronAI\Agent\Agent` class to inherit the main features of the framework and create fully functional agents.&#x20;

This class automatically manages some mechanisms for you such as memory, tools and function calls. We will go into more detail about these aspects in the following sections.

We strongly encourage to extend the Agent class instead of creating agents using the [fluent definition](#fluent-agent-definition). This strategy make it easier to add custom methods and behaviour to the agent, and also promote portability, because all the moving parts are encapsulated into a single entity that you can run wherever you want in your application, or even release as a stand alone composer package.

Let's start creating an AI Agent summarizing YouTube videos. We start creating the `YouTubeAgent` class:

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

```bash
vendor/bin/neuron make:agent App\\Neuron\\YouTubeAgent
```

{% endtab %}

{% tab title="Windows" %}

```powershell
.\vendor\bin\neuron make:agent App\Neuron\YouTubeAgent
```

{% endtab %}
{% endtabs %}

The command will create a class like this:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Agent\SystemPrompt;
use NeuronAI\Providers\AIProviderInterface;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an instance of Anthropic, OpenAI, Gemini, Ollama, etc...
    }
    
    protected function instructions(): string
    {
        return new SystemMessage(
            "You are a friendly AI Agent created with Neuron framework."
        );
    }
    
    /**
     * @return \NeuronAI\Tools\ToolInterface[]
     */
    protected function tools(): array
    {
        return [];
    }
}
```

### Monitoring & Debugging

Many of the applications you build with Neuron will contain multiple steps with multiple invocations of LLM calls. As these applications get more and more complex, it becomes crucial to be able to inspect what exactly is going on inside your agentic system. The best way to do this is with [Inspector](https://inspector.dev/).

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

### AI Provider

The minimum implementation requires assigning an AI Provider that will be the language and reasoning engine of your agent.

The only required method to implement is `provider()`  returning the instance of the provider you want to use. Let's assume it's Anthropic.

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Message\SystemMessage;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an instance of Anthropic, OpenAI, Gemini, Ollama, etc...
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    protected function instructions(): SystemMessage
    {
        return new SystemMessage(
            "You are a friendly AI Agent created with Neuron framework."
        );
    }
    
    /**
     * @return \NeuronAI\Tools\ToolInterface[]
     */
    protected function tools(): array
    {
        return [];
    }
}
```

You can also use other providers like OpenAI, Gemini, or Ollama if you want to run the model locally. Check out the [supported providers](/neuron-v4/providers/ai-provider).

### System instructions

The second important building block is the system instructions. System instructions provide directions for making the AI ​​act according to the task we want to achieve. They are fixed instructions that will be sent to the LLM on every interaction.

That’s why they are defined by an internal method, and stay encapsulated into the agent entity. Let's implement the `instructions()` method:

```php
<?php

namespace App\Neuron;

use NeuronAI\Agent\Agent;
use NeuronAI\Chat\Messages\SystemMessage;;
use NeuronAI\Providers\AIProviderInterface;
use NeuronAI\Providers\Anthropic\Anthropic;

class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        // return an AI provider instance (Anthropic, OpenAI, Ollama, Gemini, etc.)
        return new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        );
    }
    
    protected function instructions(): SystemMessage
    {
        return new SystemMessage(<<<TEXT
            You are an AI Agent specialized in writing YouTube video summaries.
            Get the url of a YouTube video, or ask the user to provide one.
            Use the tools you have available to retrieve the transcription of the video.
            Write a summary in a paragraph without using lists. Use just fluent text.
            After the summary add a list of three sentences as the three most important take away from the video.
        TEXT);
    }
    
    /**
     * @return \NeuronAI\Tools\ToolInterface[]
     */
    protected function tools(): array
    {
        return [];
    }
}
```

The SystemMessage class can be also populated with multiple content blocks SystemContent in order to dynamically inject contents into the system instructions:

```php
class YouTubeAgent extends Agent
{
    protected function provider(): AIProviderInterface
    {
        ...
    }
    
    protected function instructions(): SystemMessage
    {
        $message = new SystemMessage(<<<TEXT
            You are an AI Agent specialized in writing YouTube video summaries.
            Get the url of a YouTube video, or ask the user to provide one.
            Use the tools you have available to retrieve the transcription of the video.
        TEXT);
        
        $message->addContent(
            new SystemContent("Write a summary in a paragraph without using lists. Use just fluent text.")
        );
        
        $message->addContent(
            new SystemContent("After the summary add a list of three sentences as the three most important take away from the video.")
        );
                
        return $message;
    }
}
```

If you are willing to use the system prompt caching for providers like Anthropic, you can call the cache() method on each content part you want to cache:

```php
$message->addContent(
    new SystemContent("...")->cache()
);
```

Or call the cache method on the `SystemMessage` to cache the entire system prompt:

```php
    protected function instructions(): SystemMessage
    {
        $message = new SystemMessage(...);
        
        $message->addContent(
            new SystemContent(...)
        );
        
        $message->addContent(
            new SystemContent(...)
        );
                
        return $message->cache(); // <- Cache everything
    }
```

### Talk to the Agent

We are ready to test how the agent responds to our message based on the new instructions.

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

$message = YouTubeAgent::make()
    ->chat(new UserMessage("Who are you?"))
    ->getMessage();
    
echo $message->getContent();
// Hi, I'm a frindly AI agent specialized in summarizing YouTube videos!
// Can you give me the URL of a YouTube video you want a quick summary of?
```

### Agent State

Since the Agent is an extension of the Workflow, instead of getting the last model response with the `getMessage()` method, you cvan just run the agent workflow, and get the raw agent state as return value. The agent state contains additional information that can help you inspect what happened during the agent execution.

```php
$state = MyAgent::make()
    ->chat(new UserMessage("Who are you?"))
    ->run();

// $state is an instance of NeuropnAI\Agent\AgentState class
$state->getMessage();
```

#### Steps

Calling the `getMessage()` method you are only able to get the last message generated by the model to answer your prompt. But internally the agent can performs many tool call iterations before coming up with the final answer.

The agent state stores the list of all messages between the agent and the provider for the current execution cycle, rather than only the final answer. So you can access the list of messages with the `getSteps()` method on the agent state:

```php
$state = MyAgent::make()
    ->chat(new UserMessage("Who are you?"))
    ->run();

// Access the list of steps during the execution
foreach($state->getSteps() as $message) {
    echo "- ".$message::class."\n";
}

// The final answer
echo $state->getMessage()->getContent();
```

#### Tool Runs

If the agent decide to use tools during the execution, the agent state keeps track iof thethe number of tool runs to stop the execution if the [maxRuns](/neuron-v4/agent/tools#max-runs) limit is reached. You can access this map:

```php
$state = MyAgent::make()
    ->chat(new UserMessage("Who are you?"))
    ->run();

// Access the tool runs map
foreach($state->getToolRuns() as $toolName => $runs) {
    echo "- The tool {$toolName} was used {$runs} times\n";
}
```

### Message

The agent always accepts input as a `Message` class, and returns Message instances.

As you saw in the example above we sent a `UserMessage` instance to the agent and we retrieve the reply message that will be an `AssistantMessage` instance. A list of assistant messages and user messages creates a chat.

We will learn more about [ChatHistory](/neuron-v4/agent/chat-history-and-memory) later, but it's important to know that the unified interface for the agent input and output is the `Message` object.

<a href="/pages/P9eTDdlLvv6fmU0ydbyi" class="button primary" data-icon="arrow-right-long">Learn more about Messages</a>

### Fluent Agent Definition

In alternative to the single class encapsulation you can also instruct the agent inline using the fluent chain of methods:

```php
$agent = Agent::make()
    ->setAiProvider(
        new Anthropic(
            key: 'ANTHROPIC_API_KEY',
            model: 'ANTHROPIC_MODEL',
        )
    )
    ->setInstructions(
        new SystemMessage(...)
    )
    ->addTool([...]);
    
$message = $agent->chat(new UserMessage(...))->getMessage();
echo $message->gentContent();
```




---

[Next Page](/llms-full.txt/1)

