"""Deprecated as of LangChain v0.3.4 and will be removed in LangChain v1.0.0."""importloggingfromabcimportABC,abstractmethodfromitertoolsimportislicefromtypingimportAny,Dict,Iterable,List,Optionalfromlangchain_core._apiimportdeprecatedfromlangchain_core.language_modelsimportBaseLanguageModelfromlangchain_core.messagesimportBaseMessage,get_buffer_stringfromlangchain_core.promptsimportBasePromptTemplatefrompydanticimportBaseModel,ConfigDict,Fieldfromlangchain.chains.llmimportLLMChainfromlangchain.memory.chat_memoryimportBaseChatMemoryfromlangchain.memory.promptimport(ENTITY_EXTRACTION_PROMPT,ENTITY_SUMMARIZATION_PROMPT,)fromlangchain.memory.utilsimportget_prompt_input_keylogger=logging.getLogger(__name__)
[docs]@deprecated(since="0.3.1",removal="1.0.0",message=("Please see the migration guide at: ""https://python.langchain.com/docs/versions/migrating_memory/"),)classBaseEntityStore(BaseModel,ABC):"""Abstract base class for Entity store."""
[docs]@abstractmethoddefget(self,key:str,default:Optional[str]=None)->Optional[str]:"""Get entity value from store."""pass
[docs]@abstractmethoddefset(self,key:str,value:Optional[str])->None:"""Set entity value in store."""pass
[docs]@abstractmethoddefdelete(self,key:str)->None:"""Delete entity value from store."""pass
[docs]@abstractmethoddefexists(self,key:str)->bool:"""Check if entity exists in store."""pass
[docs]@abstractmethoddefclear(self)->None:"""Delete all entities from store."""pass
[docs]@deprecated(since="0.3.1",removal="1.0.0",message=("Please see the migration guide at: ""https://python.langchain.com/docs/versions/migrating_memory/"),)classInMemoryEntityStore(BaseEntityStore):"""In-memory Entity store."""store:Dict[str,Optional[str]]={}
[docs]@deprecated(since="0.3.1",removal="1.0.0",message=("Please see the migration guide at: ""https://python.langchain.com/docs/versions/migrating_memory/"),)classUpstashRedisEntityStore(BaseEntityStore):"""Upstash Redis backed Entity store. Entities get a TTL of 1 day by default, and that TTL is extended by 3 days every time the entity is read back. """def__init__(self,session_id:str="default",url:str="",token:str="",key_prefix:str="memory_store",ttl:Optional[int]=60*60*24,recall_ttl:Optional[int]=60*60*24*3,*args:Any,**kwargs:Any,):try:fromupstash_redisimportRedisexceptImportError:raiseImportError("Could not import upstash_redis python package. ""Please install it with `pip install upstash_redis`.")super().__init__(*args,**kwargs)try:self.redis_client=Redis(url=url,token=token)exceptException:logger.error("Upstash Redis instance could not be initiated.")self.session_id=session_idself.key_prefix=key_prefixself.ttl=ttlself.recall_ttl=recall_ttlorttl@propertydeffull_key_prefix(self)->str:returnf"{self.key_prefix}:{self.session_id}"
[docs]defget(self,key:str,default:Optional[str]=None)->Optional[str]:res=(self.redis_client.getex(f"{self.full_key_prefix}:{key}",ex=self.recall_ttl)ordefaultor"")logger.debug(f"Upstash Redis MEM get '{self.full_key_prefix}:{key}': '{res}'")returnres
[docs]defset(self,key:str,value:Optional[str])->None:ifnotvalue:returnself.delete(key)self.redis_client.set(f"{self.full_key_prefix}:{key}",value,ex=self.ttl)logger.debug(f"Redis MEM set '{self.full_key_prefix}:{key}': '{value}' EX {self.ttl}")
[docs]@deprecated(since="0.3.1",removal="1.0.0",message=("Please see the migration guide at: ""https://python.langchain.com/docs/versions/migrating_memory/"),)classRedisEntityStore(BaseEntityStore):"""Redis-backed Entity store. Entities get a TTL of 1 day by default, and that TTL is extended by 3 days every time the entity is read back. """redis_client:Anysession_id:str="default"key_prefix:str="memory_store"ttl:Optional[int]=60*60*24recall_ttl:Optional[int]=60*60*24*3def__init__(self,session_id:str="default",url:str="redis://localhost:6379/0",key_prefix:str="memory_store",ttl:Optional[int]=60*60*24,recall_ttl:Optional[int]=60*60*24*3,*args:Any,**kwargs:Any,):try:importredisexceptImportError:raiseImportError("Could not import redis python package. ""Please install it with `pip install redis`.")super().__init__(*args,**kwargs)try:fromlangchain_community.utilities.redisimportget_clientexceptImportError:raiseImportError("Could not import langchain_community.utilities.redis.get_client. ""Please install it with `pip install langchain-community`.")try:self.redis_client=get_client(redis_url=url,decode_responses=True)exceptredis.exceptions.ConnectionErroraserror:logger.error(error)self.session_id=session_idself.key_prefix=key_prefixself.ttl=ttlself.recall_ttl=recall_ttlorttl@propertydeffull_key_prefix(self)->str:returnf"{self.key_prefix}:{self.session_id}"
[docs]defget(self,key:str,default:Optional[str]=None)->Optional[str]:res=(self.redis_client.getex(f"{self.full_key_prefix}:{key}",ex=self.recall_ttl)ordefaultor"")logger.debug(f"REDIS MEM get '{self.full_key_prefix}:{key}': '{res}'")returnres
[docs]defset(self,key:str,value:Optional[str])->None:ifnotvalue:returnself.delete(key)self.redis_client.set(f"{self.full_key_prefix}:{key}",value,ex=self.ttl)logger.debug(f"REDIS MEM set '{self.full_key_prefix}:{key}': '{value}' EX {self.ttl}")
[docs]defclear(self)->None:# iterate a list in batches of size batch_sizedefbatched(iterable:Iterable[Any],batch_size:int)->Iterable[Any]:iterator=iter(iterable)whilebatch:=list(islice(iterator,batch_size)):yieldbatchforkeybatchinbatched(self.redis_client.scan_iter(f"{self.full_key_prefix}:*"),500):self.redis_client.delete(*keybatch)
[docs]@deprecated(since="0.3.1",removal="1.0.0",message=("Please see the migration guide at: ""https://python.langchain.com/docs/versions/migrating_memory/"),)classSQLiteEntityStore(BaseEntityStore):"""SQLite-backed Entity store"""session_id:str="default"table_name:str="memory_store"conn:Any=Nonemodel_config=ConfigDict(arbitrary_types_allowed=True,)def__init__(self,session_id:str="default",db_file:str="entities.db",table_name:str="memory_store",*args:Any,**kwargs:Any,):try:importsqlite3exceptImportError:raiseImportError("Could not import sqlite3 python package. ""Please install it with `pip install sqlite3`.")super().__init__(*args,**kwargs)self.conn=sqlite3.connect(db_file)self.session_id=session_idself.table_name=table_nameself._create_table_if_not_exists()@propertydeffull_table_name(self)->str:returnf"{self.table_name}_{self.session_id}"def_create_table_if_not_exists(self)->None:create_table_query=f""" CREATE TABLE IF NOT EXISTS {self.full_table_name} ( key TEXT PRIMARY KEY, value TEXT ) """withself.conn:self.conn.execute(create_table_query)
[docs]defget(self,key:str,default:Optional[str]=None)->Optional[str]:query=f""" SELECT value FROM {self.full_table_name} WHERE key = ? """cursor=self.conn.execute(query,(key,))result=cursor.fetchone()ifresultisnotNone:value=result[0]returnvaluereturndefault
[docs]defset(self,key:str,value:Optional[str])->None:ifnotvalue:returnself.delete(key)query=f""" INSERT OR REPLACE INTO {self.full_table_name} (key, value) VALUES (?, ?) """withself.conn:self.conn.execute(query,(key,value))
[docs]defdelete(self,key:str)->None:query=f""" DELETE FROM {self.full_table_name} WHERE key = ? """withself.conn:self.conn.execute(query,(key,))
[docs]defexists(self,key:str)->bool:query=f""" SELECT 1 FROM {self.full_table_name} WHERE key = ? LIMIT 1 """cursor=self.conn.execute(query,(key,))result=cursor.fetchone()returnresultisnotNone
[docs]defclear(self)->None:query=f""" DELETE FROM {self.full_table_name} """withself.conn:self.conn.execute(query)
[docs]@deprecated(since="0.3.1",removal="1.0.0",message=("Please see the migration guide at: ""https://python.langchain.com/docs/versions/migrating_memory/"),)classConversationEntityMemory(BaseChatMemory):"""Entity extractor & summarizer memory. Extracts named entities from the recent chat history and generates summaries. With a swappable entity store, persisting entities across conversations. Defaults to an in-memory entity store, and can be swapped out for a Redis, SQLite, or other entity store. """human_prefix:str="Human"ai_prefix:str="AI"llm:BaseLanguageModelentity_extraction_prompt:BasePromptTemplate=ENTITY_EXTRACTION_PROMPTentity_summarization_prompt:BasePromptTemplate=ENTITY_SUMMARIZATION_PROMPT# Cache of recently detected entity names, if any# It is updated when load_memory_variables is called:entity_cache:List[str]=[]# Number of recent message pairs to consider when updating entities:k:int=3chat_history_key:str="history"# Store to manage entity-related data:entity_store:BaseEntityStore=Field(default_factory=InMemoryEntityStore)@propertydefbuffer(self)->List[BaseMessage]:"""Access chat memory messages."""returnself.chat_memory.messages@propertydefmemory_variables(self)->List[str]:"""Will always return list of memory variables. :meta private: """return["entities",self.chat_history_key]
[docs]defload_memory_variables(self,inputs:Dict[str,Any])->Dict[str,Any]:""" Returns chat history and all generated entities with summaries if available, and updates or clears the recent entity cache. New entity name can be found when calling this method, before the entity summaries are generated, so the entity cache values may be empty if no entity descriptions are generated yet. """# Create an LLMChain for predicting entity names from the recent chat history:chain=LLMChain(llm=self.llm,prompt=self.entity_extraction_prompt)ifself.input_keyisNone:prompt_input_key=get_prompt_input_key(inputs,self.memory_variables)else:prompt_input_key=self.input_key# Extract an arbitrary window of the last message pairs from# the chat history, where the hyperparameter k is the# number of message pairs:buffer_string=get_buffer_string(self.buffer[-self.k*2:],human_prefix=self.human_prefix,ai_prefix=self.ai_prefix,)# Generates a comma-separated list of named entities,# e.g. "Jane, White House, UFO"# or "NONE" if no named entities are extracted:output=chain.predict(history=buffer_string,input=inputs[prompt_input_key],)# If no named entities are extracted, assigns an empty list.ifoutput.strip()=="NONE":entities=[]else:# Make a list of the extracted entities:entities=[w.strip()forwinoutput.split(",")]# Make a dictionary of entities with summary if exists:entity_summaries={}forentityinentities:entity_summaries[entity]=self.entity_store.get(entity,"")# Replaces the entity name cache with the most recently discussed entities,# or if no entities were extracted, clears the cache:self.entity_cache=entities# Should we return as message objects or as a string?ifself.return_messages:# Get last `k` pair of chat messages:buffer:Any=self.buffer[-self.k*2:]else:# Reuse the string we made earlier:buffer=buffer_stringreturn{self.chat_history_key:buffer,"entities":entity_summaries,}
[docs]defsave_context(self,inputs:Dict[str,Any],outputs:Dict[str,str])->None:""" Save context from this conversation history to the entity store. Generates a summary for each entity in the entity cache by prompting the model, and saves these summaries to the entity store. """super().save_context(inputs,outputs)ifself.input_keyisNone:prompt_input_key=get_prompt_input_key(inputs,self.memory_variables)else:prompt_input_key=self.input_key# Extract an arbitrary window of the last message pairs from# the chat history, where the hyperparameter k is the# number of message pairs:buffer_string=get_buffer_string(self.buffer[-self.k*2:],human_prefix=self.human_prefix,ai_prefix=self.ai_prefix,)input_data=inputs[prompt_input_key]# Create an LLMChain for predicting entity summarization from the contextchain=LLMChain(llm=self.llm,prompt=self.entity_summarization_prompt)# Generate new summaries for entities and save them in the entity storeforentityinself.entity_cache:# Get existing summary if it existsexisting_summary=self.entity_store.get(entity,"")output=chain.predict(summary=existing_summary,entity=entity,history=buffer_string,input=input_data,)# Save the updated summary to the entity storeself.entity_store.set(entity,output.strip())