packages feed

langchain-hs-core-0.0.6.0: src/Langchain/Memory/Entity.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

{- |
Module      : Langchain.Memory.Entity
Description : Structured key-value and entity fact memory
Copyright   : (c) 2025-2026 Tushar Adhatrao
License     : MIT
Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>
Stability   : experimental

Thread-safe entity and fact memory.
Retains user attributes, preferences, and extracted facts across conversation turns
for injection into prompt variables and system messages.
-}
module Langchain.Memory.Entity
  ( EntityMemory (..)
  , newEntityMemory
  , setEntity
  , getEntity
  , getAllEntities
  , deleteEntity
  , clearEntities
  , formatEntities
  , resetEntitiesToInitial
  ) where

import Control.Concurrent.STM
  ( TVar
  , atomically
  , modifyTVar'
  , newTVarIO
  , readTVarIO
  , writeTVar
  )
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import qualified Data.Text as T

import Langchain.Memory.Core (BaseMemory (..))
import Langchain.Model (systemMessage)

-- | Entity memory holding key-value facts and attributes in STM
data EntityMemory = EntityMemory
  { entityVar :: !(TVar (Map Text Text))
  , initialEntities :: !(Map Text Text)
  }

-- | Construct a new EntityMemory with optional initial facts
newEntityMemory :: MonadIO m => Map Text Text -> m EntityMemory
newEntityMemory inits = liftIO $ do
  var <- newTVarIO inits
  pure $ EntityMemory var inits

-- | Set or update a specific entity attribute
setEntity :: MonadIO m => EntityMemory -> Text -> Text -> m ()
setEntity EntityMemory {..} key val = liftIO $ atomically $ do
  modifyTVar' entityVar (Map.insert key val)

-- | Look up a specific entity attribute
getEntity :: MonadIO m => EntityMemory -> Text -> m (Maybe Text)
getEntity EntityMemory {..} key = liftIO $ do
  m <- readTVarIO entityVar
  pure $ Map.lookup key m

-- | Retrieve all current entity facts
getAllEntities :: MonadIO m => EntityMemory -> m (Map Text Text)
getAllEntities EntityMemory {..} = liftIO $ readTVarIO entityVar

-- | Delete an entity attribute
deleteEntity :: MonadIO m => EntityMemory -> Text -> m ()
deleteEntity EntityMemory {..} key = liftIO $ atomically $ do
  modifyTVar' entityVar (Map.delete key)

-- | Clear all entity attributes
clearEntities :: MonadIO m => EntityMemory -> m ()
clearEntities EntityMemory {..} = liftIO $ atomically $ do
  writeTVar entityVar Map.empty

-- | Reset entity memory back to its initial seed facts
resetEntitiesToInitial :: MonadIO m => EntityMemory -> m ()
resetEntitiesToInitial EntityMemory {..} = liftIO $ atomically $ do
  writeTVar entityVar initialEntities

-- | Format key-value entities into bullet points
formatEntities :: Map Text Text -> Text
formatEntities m
  | Map.null m = ""
  | otherwise =
      T.intercalate "\n" ["- " <> k <> ": " <> v | (k, v) <- Map.toList m]

instance BaseMemory EntityMemory where
  messages EntityMemory {..} = liftIO $ do
    m <- readTVarIO entityVar
    if Map.null m
      then pure []
      else pure [systemMessage ("Relevant context facts:\n" <> formatEntities m)]

  addMessage _ _ = pure ()

  clear = clearEntities