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

# Historial de chat

Neuron AI te proporciona un sistema integrado para gestionar la memoria de una sesión de chat que realices con el agente.

En muchas aplicaciones de preguntas y respuestas puedes mantener una conversación de ida y vuelta con el LLM, lo que significa que la aplicación necesita algún tipo de "memoria" de las preguntas y respuestas anteriores, y cierta lógica para incorporarlas a su razonamiento actual.

Por ejemplo, si haces una pregunta de seguimiento como "¿Puedes profundizar en el segundo punto?", esto no se puede entender sin el contexto de los mensajes anteriores.

En el ejemplo siguiente puedes ver cómo el Agente no conoce mi nombre al principio:

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

$message = Agent::make()
    ->chat(new UserMessage("¿Cómo me llamo?"))
    ->getMessage();

echo $message->getContent();
// Lo siento, no conozco tu nombre. ¿Quieres contarme más sobre ti?
```

Claramente el Agente no tiene ningún contexto sobre mí. Ahora intento presentarme en el primer mensaje, y luego preguntar por mi nombre:

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

$agent = Agent::make()

$message = $agent->chat(new UserMessage("¡Hola, me llamo Valerio!"))->getMessage();
echo $message->getContent();
// Hola Valerio, encantado de conocerte, ¿en qué puedo ayudarte hoy?

$message = $agent->chat(new UserMessage("¿Recuerdas mi nombre?"))->getMessage();
echo $message->getContent();
// Claro, ¡tu nombre es Valerio!
```

## Cómo funciona el historial de chat

Neuron Agent toma la lista de mensajes intercambiados entre tu aplicación y el LLM y la convierte en un objeto llamado Historial de Chat. Es una parte crucial del framework porque el historial de chat debe gestionarse en función de la ventana de contexto del LLM subyacente.

Es importante enviar los mensajes anteriores de vuelta al LLM para mantener el contexto de la conversación, pero si la lista de mensajes crece lo suficiente como para exceder la ventana de contexto del modelo, la solicitud será rechazada por el proveedor de IA, porque supera la capacidad máxima del LLM.

El historial de chat trunca automáticamente la lista de mensajes para no exceder nunca la ventana de contexto, evitando errores inesperados. Quizá quieras considerar la implementación de estrategias más sofisticadas de gestión del contexto, como [resumen](/neuron-v3-es/agente/middleware.md#summarization).

Al recortar, el historial de chat intenta minimizar la pérdida de contexto. El recortador interno puede identificar un punto de corte ligeramente menos agresivo que el identificado inicialmente. Así que, para asegurarte de que la conversación del agente se mantenga dentro del límite, **debes configurar la ventana de contexto en el historial de chat del agente con un margen del 5%-10% respecto al límite real del modelo subyacente**.

Si tu modelo trabaja con una ventana de contexto de 200K, deberías instanciar tu historial de chat con 190K, por ejemplo.

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

## Cómo alimentar una conversación previa

A veces ya tienes una representación de la conversación entre el usuario y el asistente, y necesitas una forma de alimentar al agente con mensajes anteriores.

Puedes simplemente pasar un array de mensajes al `chat()` método. Esta conversación se cargará automáticamente en la memoria del agente y podrás seguir iterando sobre ella.

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

$message = MyAgent::make()
    ->chat([
        new Message(MessageRole::USER, "Hola, mi empresa se llama Inspector.dev"),
        new Message(MessageRole::ASSISTANT, "Genial, ¿en qué puedo ayudarte hoy?"),
        new Message(MessageRole::USER, "¿Cómo se llama la empresa para la que trabajo?"),
    ])
    ->getMessage();
    
echo $message->getContent();
// Trabajas para Inspector.dev
```

El último mensaje de la lista se considerará el más reciente.

## Registrar el historial de chat

De forma predeterminada, Neuron Agent usa un historial de chat "en memoria". Eso significa que mantiene los mensajes solo durante el ciclo de ejecución actual. Pero, si quieres conservar los mensajes entre sesiones, puedes indicar al agente que use un componente diferente implementando el `chatHistory` método en la clase Agent.

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

## Implementaciones de historial de chat disponibles

### InMemoryChatHistory

Simplemente almacena la lista de mensajes en un array. Se mantiene en memoria solo durante la ejecución actual. Se usa de forma predeterminada si no registras explícitamente otro componente.

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

Este componente te permite conservar la conversación en curso con el agente en un archivo y reanudarla más tarde. Para crear una instancia de la `FileChatHistory` debes pasar la ruta absoluta del `directorio` donde quieras almacenar las conversaciones, y la `clave` única para la conversación actual.

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

El `clave` El parámetro te permite almacenar distintos archivos para separar conversaciones. Puedes usar una clave única para cada usuario, o el ID de un hilo para permitir que los usuarios almacenen varias conversaciones.

### SQLChatHistory

Este componente te permite almacenar la conversación en curso en una base de datos SQL. Antes de usar este componente debes crear la tabla en tu base de datos para almacenar los mensajes. Aquí tienes el script SQL:

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

Puedes personalizar esta tabla añadiendo más columnas, eventualmente para agregar una relación con tus usuarios o casos de uso similares. También puedes personalizar el nombre de la tabla pasando uno propio al crear la instancia.

Para crear una instancia de la `SQLChatHistory` necesitas pasar el `thread_id` para separar distintos hilos de conversación, y la `PDO` conexión a la base de datos.

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

Si tu aplicación está construida sobre un framework, puedes obtener fácilmente la conexión PDO desde el ORM. Aquí tienes un par de ejemplos en el contexto de aplicaciones Laravel o Symfony.

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

Puedes registrar tu agente como un servicio con una instancia de `Doctrine\DBAL\Connection` como dependencia del constructor:

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

Deberías crear tu propio modelo Eloquent y pasar la cadena de la clase como argumento del constructor. El modelo puede tener relaciones, scopes, atributos, etc. personalizados, pero la estructura básica debe basarse en este script de migración:

```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']); // Para un orden y recorte eficientes
});
```

#### Ejemplo de modelo ChatMessage

```php
class ChatMessage extends Model
{
    protected $fillable = [
        'thread_id', 'role', 'content', 'meta'
    ];
    
    protected $casts = [
        'content' => 'array', 
        'meta' => 'array'
    ];
    
    /**
      * devuelve BelongsTo<Conversation, $this>
     */
    public function conversation(): BelongsTo
    {
        return $this->belongsTo(Conversation::class, 'thread_id');
    }
}
```

Úsalo en tu agente:

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

## Implementa un historial de chat personalizado

Puedes crear una implementación personalizada del historial de chat para admitir una capa persistente diferente, simplemente implementando `AbstractChatHistory`. Te permite heredar varios comportamientos para la gestión interna del historial, así que solo tienes que implementar un par de métodos para guardar los mensajes en el sistema de almacenamiento que quieras usar.

```php
abstract class AbstractChatHistory implements ChatHistoryInterface
{
    /**
     * @param Message[] $messages
     */
    protected function setMessages(array $messages): void
    {
        // Gestiona guardar todo el historial de una vez cada vez que el historial se actualiza.
    }

    protected function onNewMessage(Message $message): void
    {
        // Gestiona la adición de un solo mensaje
    }

    protected function onTrimHistory(int $index): void
    {
        // Cuando se activa el recorte, 
        // los mensajes en la posición desde cero hasta $index deben eliminarse.
    }

    protected function clear(): void
    {
        // Elimina todos los mensajes.
    }
}
```

La clase abstracta ya implementa algunos métodos de utilidad para calcular el uso de tokens según las respuestas del proveedor de IA y recortar automáticamente la conversación en función del tamaño de la ventana de contexto. Solo tienes que centrarte en la interacción con el almacenamiento subyacente para añadir y eliminar mensajes, o vaciar todo el historial.

Te sugerimos encarecidamente que mires otras implementaciones como `FileChatHistory` para entender cómo crear la tuya propia.

### Serializar/Deserializar mensajes

Cuando el ChatHistory necesita almacenar un mensaje, debe serializarse. Del mismo modo, cuando se instancia el componente ChatHistory debería cargar todos los mensajes anteriores desde el almacenamiento subyacente (base de datos, caché, etc.) y deserializarlos al tipo de mensaje original.

Para serializar/deserializar mensajes de forma coherente, el `AbstractChatHistory` te proporciona `serializeMessage()` y `deserializeMessage()` métodos. Aquí tienes un ejemplo de cómo usarlos en una hipotética implementación de historial de chat con base de datos:

```php
<?php

namespace NeuronAI\Chat\History;

use NeuronAI\Chat\Messages\Message;

class DatabaseChatHistory extends AbstractChatHistory
{
    public function __construct(protected \PDO $db) 
    {
        // Recuperar la conversación actual desde el almacenamiento subyacente
        $messages = $this->db->select(...);
        
        // Deserializar e inicializar correctamente los tipos de mensaje adecuados con los datos correctos.
        $this->history = $this->deserializeMessages($messages);
    }

    protected function onNewMessage(Message $message): void
    {
        // Almacenar la versión serializada.
        $this->db->insert($message->jsonSerialize());
    }

    ...
}
```
