Source code for haive.core.tools.store_tools

"""Store tools for Haive agents.

This module provides LangChain-compatible tools that agents can use to
interact with the store system for memory management, similar to LangMem.
"""

import json
import logging
from typing import Any

from langchain_core.tools import Tool, tool
from pydantic import BaseModel, Field

from haive.core.tools.store_manager import StoreManager

logger = logging.getLogger(__name__)


[docs] class StoreMemoryInput(BaseModel): """Input schema for storing memories.""" content: str = Field(description="The memory content to store") category: str = Field( default="general", description="Category of the memory (e.g., user_preference, fact, event)", ) importance: float = Field( default=0.5, ge=0.0, le=1.0, description="Importance score from 0.0 to 1.0" ) tags: list[str] | None = Field( default=None, description="Optional tags for the memory" ) metadata: dict[str, Any] | None = Field( default=None, description="Optional additional metadata" )
[docs] class SearchMemoryInput(BaseModel): """Input schema for searching memories.""" query: str = Field(description="Search query to find relevant memories") category: str | None = Field(default=None, description="Filter by memory category") min_importance: float | None = Field( default=None, ge=0.0, le=1.0, description="Minimum importance score" ) tags: list[str] | None = Field( default=None, description="Required tags to filter by" ) limit: int = Field(default=10, ge=1, le=50, description="Maximum number of results")
[docs] class RetrieveMemoryInput(BaseModel): """Input schema for retrieving specific memories.""" memory_id: str = Field(description="The ID of the memory to retrieve")
[docs] class UpdateMemoryInput(BaseModel): """Input schema for updating memories.""" memory_id: str = Field(description="The ID of the memory to update") content: str | None = Field(default=None, description="New content for the memory") category: str | None = Field( default=None, description="New category for the memory" ) importance: float | None = Field( default=None, ge=0.0, le=1.0, description="New importance score" ) tags: list[str] | None = Field(default=None, description="New tags for the memory") metadata: dict[str, Any] | None = Field( default=None, description="Additional metadata to merge" )
[docs] class DeleteMemoryInput(BaseModel): """Input schema for deleting memories.""" memory_id: str = Field(description="The ID of the memory to delete")
[docs] def create_store_memory_tool( store_manager: StoreManager, namespace: tuple[str, ...] | None = None, tool_name: str = "store_memory", ) -> Tool: """Create a tool for storing memories. Args: store_manager: The store manager instance namespace: Optional namespace for operations tool_name: Name for the tool Returns: LangChain Tool for storing memories """ @tool(tool_name, args_schema=StoreMemoryInput) def store_memory_func( content: str, category: str = "general", importance: float = 0.5, tags: list[str] | None = None, metadata: dict[str, Any] | None = None, ) -> str: """Store important information in memory for later retrieval. Use this to remember user preferences, facts, events, or any important information. Args: content: The memory content to store category: Category of memory (user_preference, fact, event, etc.) importance: How important this memory is (0.0 to 1.0) tags: Optional tags to help categorize the memory metadata: Optional additional metadata Returns: Memory ID of the stored memory """ try: memory_id = store_manager.store_memory( content=content, category=category, importance=importance, tags=tags, metadata=metadata, namespace=namespace, ) result = { "success": True, "memory_id": memory_id, "message": f"Successfully stored memory with ID: {memory_id}", } logger.debug(f"Stored memory: {memory_id}") return json.dumps(result) except Exception as e: error_result = { "success": False, "error": str(e), "message": "Failed to store memory", } logger.exception(f"Failed to store memory: {e}") return json.dumps(error_result) return store_memory_func
[docs] def create_search_memory_tool( store_manager: StoreManager, namespace: tuple[str, ...] | None = None, tool_name: str = "search_memory", ) -> Tool: """Create a tool for searching memories. Args: store_manager: The store manager instance namespace: Optional namespace for operations tool_name: Name for the tool Returns: LangChain Tool for searching memories """ @tool(tool_name, args_schema=SearchMemoryInput) def search_memory_func( query: str, category: str | None = None, min_importance: float | None = None, tags: list[str] | None = None, limit: int = 10, ) -> str: """Search for relevant memories based on a query. Use this to recall information about users, facts, or past events. Args: query: Search query to find relevant memories category: Filter by memory category min_importance: Minimum importance score to filter by tags: Required tags to filter by limit: Maximum number of results to return Returns: JSON string with search results """ try: memories = store_manager.search_memories( query=query, category=category, min_importance=min_importance, tags=tags, limit=limit, namespace=namespace, ) results = [] for memory in memories: results.append( { "id": memory.id, "content": memory.content, "category": memory.category, "importance": memory.importance, "tags": memory.tags, "created_at": memory.created_at.isoformat(), "updated_at": memory.updated_at.isoformat(), } ) result = { "success": True, "memories": results, "count": len(results), "message": f"Found {len(results)} relevant memories", } logger.debug(f"Search returned {len(results)} memories for query: {query}") return json.dumps(result, indent=2) except Exception as e: error_result = { "success": False, "error": str(e), "message": "Failed to search memories", } logger.exception(f"Failed to search memories: {e}") return json.dumps(error_result) return search_memory_func
[docs] def create_retrieve_memory_tool( store_manager: StoreManager, namespace: tuple[str, ...] | None = None, tool_name: str = "retrieve_memory", ) -> Tool: """Create a tool for retrieving specific memories. Args: store_manager: The store manager instance namespace: Optional namespace for operations tool_name: Name for the tool Returns: LangChain Tool for retrieving memories """ @tool(tool_name, args_schema=RetrieveMemoryInput) def retrieve_memory_func(memory_id: str) -> str: """Retrieve a specific memory by its ID. Args: memory_id: The ID of the memory to retrieve Returns: JSON string with the memory details """ try: memory = store_manager.retrieve_memory(memory_id, namespace=namespace) if memory is None: result = { "success": False, "message": f"Memory with ID {memory_id} not found", } else: result = { "success": True, "memory": { "id": memory.id, "content": memory.content, "category": memory.category, "importance": memory.importance, "tags": memory.tags, "metadata": memory.metadata, "created_at": memory.created_at.isoformat(), "updated_at": memory.updated_at.isoformat(), }, "message": f"Successfully retrieved memory {memory_id}", } logger.debug(f"Retrieved memory: {memory_id}") return json.dumps(result, indent=2) except Exception as e: error_result = { "success": False, "error": str(e), "message": f"Failed to retrieve memory {memory_id}", } logger.exception(f"Failed to retrieve memory {memory_id}: {e}") return json.dumps(error_result) return retrieve_memory_func
[docs] def create_update_memory_tool( store_manager: StoreManager, namespace: tuple[str, ...] | None = None, tool_name: str = "update_memory", ) -> Tool: """Create a tool for updating memories. Args: store_manager: The store manager instance namespace: Optional namespace for operations tool_name: Name for the tool Returns: LangChain Tool for updating memories """ @tool(tool_name, args_schema=UpdateMemoryInput) def update_memory_func( memory_id: str, content: str | None = None, category: str | None = None, importance: float | None = None, tags: list[str] | None = None, metadata: dict[str, Any] | None = None, ) -> str: """Update an existing memory with new information. Args: memory_id: The ID of the memory to update content: New content for the memory category: New category for the memory importance: New importance score tags: New tags for the memory metadata: Additional metadata to merge Returns: JSON string with update status """ try: success = store_manager.update_memory( memory_id=memory_id, content=content, category=category, importance=importance, tags=tags, metadata=metadata, namespace=namespace, ) if success: result = { "success": True, "message": f"Successfully updated memory {memory_id}", } else: result = { "success": False, "message": f"Memory with ID {memory_id} not found", } logger.debug(f"Updated memory: {memory_id}, success: {success}") return json.dumps(result) except Exception as e: error_result = { "success": False, "error": str(e), "message": f"Failed to update memory {memory_id}", } logger.exception(f"Failed to update memory {memory_id}: {e}") return json.dumps(error_result) return update_memory_func
[docs] def create_delete_memory_tool( store_manager: StoreManager, namespace: tuple[str, ...] | None = None, tool_name: str = "delete_memory", ) -> Tool: """Create a tool for deleting memories. Args: store_manager: The store manager instance namespace: Optional namespace for operations tool_name: Name for the tool Returns: LangChain Tool for deleting memories """ @tool(tool_name, args_schema=DeleteMemoryInput) def delete_memory_func(memory_id: str) -> str: """Delete a memory by its ID. Use with caution as this action cannot be undone. Args: memory_id: The ID of the memory to delete Returns: JSON string with deletion status """ try: success = store_manager.delete_memory(memory_id, namespace=namespace) if success: result = { "success": True, "message": f"Successfully deleted memory {memory_id}", } else: result = { "success": False, "message": f"Memory with ID {memory_id} not found", } logger.debug(f"Deleted memory: {memory_id}, success: {success}") return json.dumps(result) except Exception as e: error_result = { "success": False, "error": str(e), "message": f"Failed to delete memory {memory_id}", } logger.exception(f"Failed to delete memory {memory_id}: {e}") return json.dumps(error_result) return delete_memory_func
[docs] def create_memory_tools_suite( store_manager: StoreManager, namespace: tuple[str, ...] | None = None, include_tools: list[str] | None = None, ) -> list[Tool]: """Create a complete suite of memory tools. Args: store_manager: The store manager instance namespace: Optional namespace for operations include_tools: Optional list of tools to include (store, search, retrieve, update, delete) Returns: List of memory management tools """ available_tools = { "store": lambda: create_store_memory_tool(store_manager, namespace), "search": lambda: create_search_memory_tool(store_manager, namespace), "retrieve": lambda: create_retrieve_memory_tool(store_manager, namespace), "update": lambda: create_update_memory_tool(store_manager, namespace), "delete": lambda: create_delete_memory_tool(store_manager, namespace), } if include_tools is None: include_tools = ["store", "search", "retrieve", "update", "delete"] tools = [] for tool_name in include_tools: if tool_name in available_tools: tools.append(available_tools[tool_name]()) else: logger.warning(f"Unknown tool requested: {tool_name}") logger.info(f"Created {len(tools)} memory tools: {include_tools}") return tools
# Convenience function similar to LangMem's API
[docs] def create_manage_memory_tool( store_manager: StoreManager, namespace: tuple[str, ...] | None = None ) -> Tool: """Create a manage memory tool (alias for store_memory_tool). This provides compatibility with LangMem-style naming. """ return create_store_memory_tool(store_manager, namespace, "manage_memory")
[docs] def create_search_memory_tool_alias( store_manager: StoreManager, namespace: tuple[str, ...] | None = None ) -> Tool: """Create a search memory tool (alias for better naming consistency). This provides compatibility with LangMem-style naming. """ return create_search_memory_tool(store_manager, namespace, "search_memory")