> 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

Aprende cómo Neuron AI gestiona conversaciones de múltiples turnos.

Neuron AI te proporciona un sistema integrado para gestionar la memoria de una sesión de chat que realizas 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 preguntas y respuestas anteriores, y alguna lógica para incorporarlas a su razonamiento actual.

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

En el ejemplo siguiente puedes ver cómo el Agente no sabe 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 pruebo a 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 del 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 superar 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 del chat trunca automáticamente la lista de mensajes para no superar 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).

Durante el recorte, el historial del 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 mantiene dentro del límite, **deberías 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 anterior

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 pasar simplemente 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

Por defecto, Neuron Agent utiliza un historial de chat "en memoria". Eso significa que solo conserva los mensajes durante el ciclo de ejecución actual. Pero, si quieres persistir los mensajes entre sesiones, puedes decirle 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
        );
    }
}
```

### InMemoryChatHistory

Simplemente almacena la lista de mensajes en un array. Se mantiene en memoria solo durante la ejecución actual. Se usa por defecto 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 persistir la conversación en curso con el agente en un archivo y reanudarla más tarde. Para crear una instancia de `FileChatHistory` necesitas 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` 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 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 añadir una relación con tus usuarios o casos de uso similares. También puedes personalizar el nombre de la tabla pasando el tuyo propio al crear la instancia.

Para crear una instancia de `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, ámbitos, 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 ordenamiento 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'
    ];
    
    /**
     * return 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
        );
    }
}
```

## Implementar historial de chat personalizado

Puedes crear una implementación personalizada del historial de chat para admitir una capa persistente diferente simplemente implementando `AbstractChatHistory`. Esto te permite heredar varios comportamientos para la gestión interna del historial, de modo que solo tienes que implementar un par de métodos para guardar 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 se actualiza el historial.
    }

    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
    {
        // Eliminar todos los mensajes.
    }
}
```

La clase abstracta ya implementa algunos métodos de utilidad para calcular el uso de tokens basándose en 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 borrar todo el historial.

Recomendamos encarecidamente revisar otras implementaciones como `FileChatHistory` para entender cómo crear la tuya propia.

### Serializar/Deserializar mensajes

Cuando ChatHistory necesita almacenar un mensaje, este 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 en 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());
    }

    ...
}
```
