Hay varios casos de uso donde resulta valioso mantener un almacén de hechos útiles que puedan añadirse inteligentemente al contexto del agente justo antes de un paso específico. El caso típico aquí es un patrón RAG, donde una consulta se utiliza para recuperar información relevante de una base de datos que luego se añade al contexto del agente.
Saptiva-Agents proporciona un protocolo de memoria llamado Memory que se puede extender para proporcionar esta funcionalidad. Los métodos clave son query, update_context, add, clear y close.
add: agrega nuevas entradas al almacén de memoria
query: recupera información relevante del almacén de memoria
update_context: modifica el model_context interno del agente agregando la información recuperada (utilizado en la clase AssistantAgent)
clear: borra todas las entradas del almacén de memoria
close: limpia cualquier recurso utilizado por el almacén de memoria
Ejemplo de ListMemory
ListMemory es una implementación de ejemplo del protocolo Memory. Es una implementación simple basada en una lista que mantiene los recuerdos en orden cronológico, agregando los más recientes al contexto del modelo. Esta implementación es directa y predecible, lo que facilita su comprensión y depuración.
from saptiva_agents import QWEN_MODEL
from saptiva_agents.agents import AssistantAgent
from saptiva_agents.ui import Console
from saptiva_agents.memory import ListMemory, MemoryContent, MemoryMimeType
from saptiva_agents.base import SaptivaAIChatCompletionClient
# Inicializar la memoria del usuario
user_memory = ListMemory()
# Agregar preferencias del usuario a la memoria
await user_memory.add(MemoryContent(content="The weather should be in metric units", mime_type=MemoryMimeType.TEXT))
await user_memory.add(MemoryContent(content="Meal recipe must be vegan", mime_type=MemoryMimeType.TEXT))
async def get_weather(city: str, units: str = "imperial") -> str:
if units == "imperial":
return f"The weather in {city} is 73 °F and Sunny."
elif units == "metric":
return f"The weather in {city} is 23 °C and Sunny."
else:
return f"Sorry, I don't know the weather in {city}."
assistant_agent = AssistantAgent(
name="assistant_agent",
model_client=SaptivaAIChatCompletionClient(
model=QWEN_MODEL,
api_key="TU_SAPTIVA_API_KEY",
),
tools=[get_weather],
memory=[user_memory],
)
# Ejecutar el agente con una tarea
stream = assistant_agent.run_stream(task="What is the weather in New York?")
await Console(stream)
---------- user ----------
What is the weather in New York?
---------- assistant_agent ----------
[MemoryContent(content='The weather should be in metric units', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None), MemoryContent(content='Meal recipe must be vegan', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None)]
---------- assistant_agent ----------
[FunctionCall(id='call_GpimUGED5zUbfxORaGo2JD6F', arguments='{"city":"New York","units":"metric"}', name='get_weather')]
---------- assistant_agent ----------
[FunctionExecutionResult(content='The weather in New York is 23 °C and Sunny.', call_id='call_GpimUGED5zUbfxORaGo2JD6F', is_error=False)]
---------- assistant_agent ----------
The weather in New York is 23 °C and Sunny.
TaskResult(messages=[TextMessage(source='user', models_usage=None, metadata={}, content='What is the weather in New York?', type='TextMessage'), MemoryQueryEvent(source='assistant_agent', models_usage=None, metadata={}, content=[MemoryContent(content='The weather should be in metric units', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None), MemoryContent(content='Meal recipe must be vegan', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None)], type='MemoryQueryEvent'), ToolCallRequestEvent(source='assistant_agent', models_usage=RequestUsage(prompt_tokens=123, completion_tokens=20), metadata={}, content=[FunctionCall(id='call_GpimUGED5zUbfxORaGo2JD6F', arguments='{"city":"New York","units":"metric"}', name='get_weather')], type='ToolCallRequestEvent'), ToolCallExecutionEvent(source='assistant_agent', models_usage=None, metadata={}, content=[FunctionExecutionResult(content='The weather in New York is 23 °C and Sunny.', call_id='call_GpimUGED5zUbfxORaGo2JD6F', is_error=False)], type='ToolCallExecutionEvent'), ToolCallSummaryMessage(source='assistant_agent', models_usage=None, metadata={}, content='The weather in New York is 23 °C and Sunny.', type='ToolCallSummaryMessage')], stop_reason=None)
Podemos inspeccionar que el model_context del assistant_agent se actualiza realmente con las entradas de memoria recuperadas. El método transform se utiliza para formatear dichas entradas en una cadena de texto que pueda ser utilizada por el agente. En este caso, simplemente concatenamos el contenido de cada entrada de memoria en una única cadena.
[UserMessage(content='What is the weather in New York?', source='user', type='UserMessage'),
SystemMessage(content='\nRelevant memory content (in chronological order):\n1. The weather should be in metric units\n2. Meal recipe must be vegan\n', type='SystemMessage'),
AssistantMessage(content=[FunctionCall(id='call_GpimUGED5zUbfxORaGo2JD6F', arguments='{"city":"New York","units":"metric"}', name='get_weather')], thought=None, source='assistant_agent', type='AssistantMessage'),
FunctionExecutionResultMessage(content=[FunctionExecutionResult(content='The weather in New York is 23 °C and Sunny.', call_id='call_GpimUGED5zUbfxORaGo2JD6F', is_error=False)], type='FunctionExecutionResultMessage')]
Vemos arriba que el clima se devuelve en grados centígrados, tal como se indica en las preferencias del usuario.
De manera similar, si asumimos que hacemos una pregunta distinta sobre generar un plan de comidas, el agente es capaz de recuperar información relevante de la memoria y proporcionar una respuesta personalizada (vegana).
stream = assistant_agent.run_stream(task="Write brief meal recipe with broth")
await Console(stream)
---------- user ----------
Write brief meal recipe with broth
---------- assistant_agent ----------
[MemoryContent(content='The weather should be in metric units', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None), MemoryContent(content='Meal recipe must be vegan', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None)]
---------- assistant_agent ----------
Here's a brief vegan broth recipe:
**Vegan Vegetable Broth**
**Ingredients:**
- 2 tablespoons olive oil
- 1 large onion, chopped
- 2 carrots, sliced
- 2 celery stalks, sliced
- 4 cloves garlic, minced
- 1 teaspoon salt
- 1/2 teaspoon pepper
- 1 bay leaf
- 1 teaspoon thyme
- 1 teaspoon rosemary
- 8 cups water
- 1 cup mushrooms, sliced
- 1 cup chopped leafy greens (e.g., kale, spinach)
- 1 tablespoon soy sauce (optional)
**Instructions:**
1. **Sauté Vegetables:** In a large pot, heat olive oil over medium heat. Add the onion, carrots, and celery, and sauté until they begin to soften, about 5-7 minutes.
2. **Add Garlic & Seasonings:** Stir in the garlic, salt, pepper, bay leaf, thyme, and rosemary. Cook for another 2 minutes until fragrant.
3. **Simmer Broth:** Pour in the water, add mushrooms and soy sauce (if using). Increase heat and bring to a boil. Reduce heat and let it simmer for 30-45 minutes.
4. **Add Greens:** In the last 5 minutes of cooking, add the chopped leafy greens.
5. **Strain & Serve:** Remove from heat, strain out the vegetables (or leave them in for a chunkier texture), and adjust seasoning if needed. Serve hot as a base for soups or enjoy as is!
Enjoy your flavorful, nourishing vegan broth!
TaskResult(messages=[TextMessage(source='user', models_usage=None, metadata={}, content='Write brief meal recipe with broth', type='TextMessage'), MemoryQueryEvent(source='assistant_agent', models_usage=None, metadata={}, content=[MemoryContent(content='The weather should be in metric units', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None), MemoryContent(content='Meal recipe must be vegan', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None)], type='MemoryQueryEvent'), TextMessage(source='assistant_agent', models_usage=RequestUsage(prompt_tokens=208, completion_tokens=331), metadata={}, content="Here's a brief vegan broth recipe:\n\n**Vegan Vegetable Broth**\n\n**Ingredients:**\n- 2 tablespoons olive oil\n- 1 large onion, chopped\n- 2 carrots, sliced\n- 2 celery stalks, sliced\n- 4 cloves garlic, minced\n- 1 teaspoon salt\n- 1/2 teaspoon pepper\n- 1 bay leaf\n- 1 teaspoon thyme\n- 1 teaspoon rosemary\n- 8 cups water\n- 1 cup mushrooms, sliced\n- 1 cup chopped leafy greens (e.g., kale, spinach)\n- 1 tablespoon soy sauce (optional)\n\n**Instructions:**\n\n1. **Sauté Vegetables:** In a large pot, heat olive oil over medium heat. Add the onion, carrots, and celery, and sauté until they begin to soften, about 5-7 minutes.\n\n2. **Add Garlic & Seasonings:** Stir in the garlic, salt, pepper, bay leaf, thyme, and rosemary. Cook for another 2 minutes until fragrant.\n\n3. **Simmer Broth:** Pour in the water, add mushrooms and soy sauce (if using). Increase heat and bring to a boil. Reduce heat and let it simmer for 30-45 minutes.\n\n4. **Add Greens:** In the last 5 minutes of cooking, add the chopped leafy greens.\n\n5. **Strain & Serve:** Remove from heat, strain out the vegetables (or leave them in for a chunkier texture), and adjust seasoning if needed. Serve hot as a base for soups or enjoy as is!\n\nEnjoy your flavorful, nourishing vegan broth! TERMINATE", type='TextMessage')], stop_reason=None)
Almacenes de Memoria Personalizados (bases vectoriales, etc.)
Puedes construir tu propia implementación del protocolo Memory para incorporar almacenes más complejos. Por ejemplo, un almacén que use una base de datos vectorial para recuperar información similar, o incluso un modelo de aprendizaje automático para personalizar respuestas.
Debes sobrecargar los métodos add, query y update_context, y luego pasar ese almacén personalizado al agente.
Ejemplo con base vectorial usando ChromaDB:
import os
from pathlib import Path
from saptiva_agents import QWEN_MODEL
from saptiva_agents.agents import AssistantAgent
from saptiva_agents.ui import Console
from saptiva_agents.memory import MemoryContent, MemoryMimeType
from saptiva_agents.memory.chromadb import ChromaDBVectorMemory, PersistentChromaDBVectorMemoryConfig
from saptiva_agents.base import SaptivaAIChatCompletionClient
# Inicializar la memoria ChromaDB con configuración personalizada
chroma_user_memory = ChromaDBVectorMemory(
config=PersistentChromaDBVectorMemoryConfig(
collection_name="preferences",
persistence_path=os.path.join(str(Path.home()), ".chromadb_saptiva_agents"),
k=2, # Devolver los 2 resultados más relevantes
score_threshold=0.4, # Puntuación mínima de similitud
)
)
# Agregar preferencias del usuario a la memoria
await chroma_user_memory.add(
MemoryContent(
content="The weather should be in metric units",
mime_type=MemoryMimeType.TEXT,
metadata={"category": "preferences", "type": "units"},
)
)
await chroma_user_memory.add(
MemoryContent(
content="Meal recipe must be vegan",
mime_type=MemoryMimeType.TEXT,
metadata={"category": "preferences", "type": "dietary"},
)
)
# Crear agente con memoria ChromaDB
assistant_agent = AssistantAgent(
name="assistant_agent",
model_client=SaptivaAIChatCompletionClient(
model=QWEN_MODEL,
api_key="TU_SAPTIVA_API_KEY",
),
tools=[get_weather],
memory=[user_memory],
)
# Ejecutar tarea
stream = assistant_agent.run_stream(task="What is the weather in New York?")
await Console(stream)
await user_memory.close()
---------- user ----------
What is the weather in New York?
---------- assistant_agent ----------
[MemoryContent(content='The weather should be in metric units', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None), MemoryContent(content='Meal recipe must be vegan', mime_type=<MemoryMimeType.TEXT: 'text/plain'>, metadata=None)]
---------- assistant_agent ----------
[FunctionCall(id='call_PKcfeEHXimGG2QOhJwXzCLuZ', arguments='{"city":"New York","units":"metric"}', name='get_weather')]
---------- assistant_agent ----------
[FunctionExecutionResult(content='The weather in New York is 23 °C and Sunny.', call_id='call_PKcfeEHXimGG2QOhJwXzCLuZ', is_error=False)]
---------- assistant_agent ----------
The weather in New York is 23 °C and Sunny.
También puedes serializar la memoria ChromaDBVectorMemory y guardarla en disco para reutilizarla más adelante:
'{"provider":"autogen_ext.memory.chromadb.ChromaDBVectorMemory","component_type":"memory","version":1,"component_version":1,"description":"ChromaDB-based vector memory implementation with similarity search.","label":"ChromaDBVectorMemory","config":{"client_type":"persistent","collection_name":"preferences","distance_metric":"cosine","k":2,"score_threshold":0.4,"allow_reset":false,"tenant":"default_tenant","database":"default_database","persistence_path":"/Users/victordibia/.chromadb_saptiva_agents"}}'
Agente RAG: Unificándolo Todo
El patrón RAG (Retrieval Augmented Generation), común en la construcción de sistemas de IA, abarca dos fases distintas:
Indexación: Cargar documentos, dividirlos en fragmentos (chunking) y almacenarlos en una base de datos vectorial.
Recuperación: Buscar y utilizar fragmentos relevantes durante el tiempo de ejecución de una conversación.
En nuestros ejemplos anteriores, agregamos elementos manualmente a la memoria y los pasamos a nuestros agentes. En la práctica, el proceso de indexación suele estar automatizado y se basa en fuentes documentales mucho más grandes como documentación de productos, archivos internos o bases de conocimiento.