run-llama / llama_index

LlamaIndex is a data framework for your LLM applications
https://docs.llamaindex.ai
MIT License
35.42k stars 4.99k forks source link

[Question]: Session Manangement using Redis Chat Store with ChatMemoryBuffer guide #15035

Open avisheka93 opened 1 month ago

avisheka93 commented 1 month ago

Question Validation

Question

Im building a enterprise grade chatbot using llamaindex condense context chat engine, python and using redis chat store for chat conversation memory..i need to ensure session management which includes both Chronological Messaging and Concurrent Session management capabilities any guidance for that

dosubot[bot] commented 1 month ago

To implement session management with both chronological messaging and concurrent session management capabilities using the CondensePlusContextChatEngine from LlamaIndex and Redis as the chat store, you can follow these steps:

  1. Chronological Messaging: Ensure that each message is stored in the correct order. This is already handled by the ChatMemoryBuffer in the provided code, which stores and retrieves messages in sequence.

  2. Concurrent Session Management: Use Redis to manage multiple sessions concurrently. Each session can be identified by a unique session ID, and Redis can store the chat history for each session separately.

Here is an example implementation:

Step 1: Set Up Redis for Session Management

First, install the Redis client for Python if you haven't already:

pip install redis

Step 2: Use RedisChatStore for Session Management

You can utilize the RedisChatStore class from LlamaIndex to handle storing and retrieving chat history in Redis. Here is an example of how you can do this:

import redis
from llama_index.legacy.llms import ChatMessage
from llama_index.legacy.storage.chat_store.redis_chat_store import RedisChatStore

# Initialize Redis client
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)

# Example usage
session_id = "unique_session_id"
chat_store = RedisChatStore(redis_client=redis_client)

# Set up the chat engine with Redis-based memory
chat_engine = CondensePlusContextChatEngine.from_defaults(
    retriever=retriever,
    memory=chat_store,
    # other parameters...
)

# Example chat session
session_id = "user_123_session"
chat_store.set_messages(session_id, [])

# Chat with the engine
response = chat_engine.chat("Hello, how are you?", session_id=session_id)
print(response.response)

# Reset the session
chat_store.delete_messages(session_id)

Step 3: Using the Chat Engine

You can now use the chat_engine to handle multiple sessions concurrently. Each session will have its own unique session ID, and the chat history will be stored in Redis.

# Example chat session
session_id = "user_123_session"
chat_store.set_messages(session_id, [])

# Chat with the engine
response = chat_engine.chat("Hello, how are you?", session_id=session_id)
print(response.response)

# Reset the session
chat_store.delete_messages(session_id)

This setup ensures that each session's chat history is stored and retrieved in chronological order, and multiple sessions can be managed concurrently using Redis. The CondensePlusContextChatEngine class from LlamaIndex is designed to handle the conversation flow by condensing the conversation history and building context for the standalone question, making it suitable for enterprise-grade chatbots [1][2][3].

To continue talking to Dosu, mention @dosu.

avisheka93 commented 1 month ago

@dosu im using redis hosted in Azure so i have passed redis url and created chat store

chat_store = RedisChatStore(redis_url=REDIS_CONNECTION_STRING, ttl=300)

chat_memory = ChatMemoryBuffer.from_defaults( token_limit=3900, chat_store=chat_store, chat_store_key="user1", )

chat_engine = index.as_chat_engine( chat_mode="condense_plus_context", memory=chat_memory, llm=llm, context_prompt=ContextPrompt, condense_prompt= CondensePrompt, streaming = True, verbose=True, )

should i pass session id in chat engine?

dosubot[bot] commented 1 month ago

Yes, you should pass a session ID to the chat engine to manage concurrent sessions effectively. This ensures that each session's chat history is stored and retrieved separately. You can modify your setup to include a session ID as follows:

  1. Modify the ContextChatEngine class to include a session_id parameter:

    • Update the CondensePlusContextChatEngine to handle the session_id.
  2. Pass the session_id when calling the chat method:

    • Ensure that the session_id is passed to the chat method to manage the session-specific data.

Here is an example of how you can modify your setup:

# Initialize Redis chat store with Azure Redis URL
chat_store = RedisChatStore(redis_url=REDIS_CONNECTION_STRING, ttl=300)

# Create ChatMemoryBuffer with the Redis chat store
chat_memory = ChatMemoryBuffer.from_defaults(
    token_limit=3900,
    chat_store=chat_store,
    chat_store_key="user1",
)

# Create the chat engine with the necessary parameters
chat_engine = index.as_chat_engine(
    chat_mode="condense_plus_context",
    memory=chat_memory,
    llm=llm,
    context_prompt=ContextPrompt,
    condense_prompt=CondensePrompt,
    streaming=True,
    verbose=True,
)

# Example session ID
session_id = "unique_session_id"

# Chat with the engine using the session ID
response = chat_engine.chat("Hello, how are you?", session_id=session_id)
print(response.response)

# Reset the session if needed
chat_store.delete_messages(session_id)

This setup ensures that each session is managed separately using the session_id, allowing for effective concurrent session management [1][2].

To continue talking to Dosu, mention @dosu.

avisheka93 commented 1 month ago

@dosu this will preserve the session id, user id and conversation history in redis ? all of them per user per session?

dosubot[bot] commented 1 month ago

Yes, the setup will preserve the session ID, user ID, and conversation history in Redis, ensuring that each user's session is managed separately. Here's how it works:

  1. Session ID and User ID: Each session is identified by a unique session ID, which can be associated with a user ID. This allows you to manage multiple sessions for different users concurrently.

  2. Conversation History: The conversation history for each session is stored in Redis using the session ID as the key. This ensures that the chat history is preserved and can be retrieved for each specific session.

Here is an example of how you can manage this:

# Initialize Redis chat store with Azure Redis URL
chat_store = RedisChatStore(redis_url=REDIS_CONNECTION_STRING, ttl=300)

# Create ChatMemoryBuffer with the Redis chat store
chat_memory = ChatMemoryBuffer.from_defaults(
    token_limit=3900,
    chat_store=chat_store,
    chat_store_key="user1",  # This can be dynamically set per user/session
)

# Create the chat engine with the necessary parameters
chat_engine = index.as_chat_engine(
    chat_mode="condense_plus_context",
    memory=chat_memory,
    llm=llm,
    context_prompt=ContextPrompt,
    condense_prompt=CondensePrompt,
    streaming=True,
    verbose=True,
)

# Example session ID and user ID
session_id = "unique_session_id"
user_id = "user1"

# Chat with the engine using the session ID
response = chat_engine.chat("Hello, how are you?", session_id=session_id)
print(response.response)

# Reset the session if needed
chat_store.delete_messages(session_id)

In this setup:

This approach ensures that the session ID, user ID, and conversation history are all preserved in Redis, allowing for effective session management per user and per session [1][2].

To continue talking to Dosu, mention @dosu.

avisheka93 commented 1 month ago

Thanks

avisheka93 commented 1 month ago

@dosu chat_store = RedisChatStore(redis_url=url, ttl=300) This doesnt connect to Redis for me cant we pass ssl or other params with this

dosubot[bot] commented 1 month ago

To connect to Redis with SSL or other parameters, you can use the redis.StrictRedis client directly and pass it to the RedisChatStore. Here’s how you can do it:

import redis
from llama_index.legacy.storage.chat_store.redis_chat_store import RedisChatStore

# Initialize Redis client with SSL and other parameters
redis_client = redis.StrictRedis.from_url(
    url,
    ssl=True,
    ssl_cert_reqs=None,  # Adjust as needed
    # Add other parameters as required
)

# Create RedisChatStore with the Redis client
chat_store = RedisChatStore(redis_client=redis_client, ttl=300)

# Example usage with ChatMemoryBuffer and chat engine
chat_memory = ChatMemoryBuffer.from_defaults(
    token_limit=3900,
    chat_store=chat_store,
    chat_store_key="user1",
)

chat_engine = index.as_chat_engine(
    chat_mode="condense_plus_context",
    memory=chat_memory,
    llm=llm,
    context_prompt=ContextPrompt,
    condense_prompt=CondensePrompt,
    streaming=True,
    verbose=True,
)

# Example session ID and user ID
session_id = "unique_session_id"
user_id = "user1"

# Chat with the engine using the session ID
response = chat_engine.chat("Hello, how are you?", session_id=session_id)
print(response.response)

# Reset the session if needed
chat_store.delete_messages(session_id)

Alternatively, you can create a custom class to include SSL or other connection parameters for a secure connection to your Redis instance hosted on Azure:

from redis import Redis
from llama_index.legacy.storage.chat_store.redis_chat_store import RedisChatStore

class CustomRedisChatStore(RedisChatStore):
    def __init__(self, redis_url: str, ttl: Optional[int] = None, **kwargs: Any) -> None:
        # Add SSL parameters to kwargs
        kwargs.update({
            "ssl": True,
            "ssl_cert_reqs": "required",
            "ssl_ca_certs": "/path/to/ca-cert.pem",
            "ssl_certfile": "/path/to/client-cert.pem",
            "ssl_keyfile": "/path/to/client-key.pem"
        })
        super().__init__(redis_url=redis_url, ttl=ttl, **kwargs)

# Example usage
redis_url = "rediss://<your-redis-url>:6380"
chat_store = CustomRedisChatStore(redis_url=redis_url, ttl=300)

In this example, the kwargs dictionary is updated with the necessary SSL parameters before being passed to the RedisChatStore initialization. Adjust the paths to the certificate files and other parameters as needed for your specific setup [1][2].

To continue talking to Dosu, mention @dosu.

avisheka93 commented 1 month ago

@dosu llamaindex chat_engine.chat do not take session id as a parameter

dosubot[bot] commented 1 month ago

The CondensePlusContextChatEngine does not natively support passing a session ID as a parameter to the chat method. Instead, you can manage session-specific data by handling the session ID within the ChatMemoryBuffer and RedisChatStore.

Here's how you can manage session-specific data:

  1. Store and Retrieve Messages Using Session ID:

    • Use the session ID to store and retrieve messages in the RedisChatStore.
  2. Modify the ChatMemoryBuffer to Use Session ID:

    • Ensure that the ChatMemoryBuffer uses the session ID to manage chat history.

Here is an example implementation:

Step 1: Initialize Redis Chat Store with Session ID

import redis
from llama_index.legacy.storage.chat_store.redis_chat_store import RedisChatStore
from llama_index.legacy.memory.chat_memory_buffer import ChatMemoryBuffer

# Initialize Redis client with SSL and other parameters
redis_client = redis.StrictRedis.from_url(
    url,
    ssl=True,
    ssl_cert_reqs=None,  # Adjust as needed
    # Add other parameters as required
)

# Create RedisChatStore with the Redis client
chat_store = RedisChatStore(redis_client=redis_client, ttl=300)

Step 2: Create ChatMemoryBuffer with Session ID

# Example session ID
session_id = "unique_session_id"

# Create ChatMemoryBuffer with the Redis chat store and session ID
chat_memory = ChatMemoryBuffer.from_defaults(
    token_limit=3900,
    chat_store=chat_store,
    chat_store_key=session_id,  # Use session ID as the chat store key
)

Step 3: Create and Use the Chat Engine

# Create the chat engine with the necessary parameters
chat_engine = index.as_chat_engine(
    chat_mode="condense_plus_context",
    memory=chat_memory,
    llm=llm,
    context_prompt=ContextPrompt,
    condense_prompt=CondensePrompt,
    streaming=True,
    verbose=True,
)

# Chat with the engine using the session ID
response = chat_engine.chat("Hello, how are you?")
print(response.response)

# Reset the session if needed
chat_store.delete_messages(session_id)

In this setup:

This approach ensures that the session-specific data is handled correctly, even though the chat method does not directly take a session ID as a parameter [1][2].

To continue talking to Dosu, mention @dosu.

avisheka93 commented 1 month ago

@dosu but i also want to take into account user id per session id to maintain both chronology and concurrency both session id and user id will be needed correct?

dosubot[bot] commented 1 month ago

Yes, to maintain both chronological messaging and concurrent session management, you should take into account both the session ID and user ID. This ensures that each user's session is managed separately and the chat history is preserved in the correct order.

Here's how you can modify your setup to include both session ID and user ID:

  1. Modify the RedisChatStore to Use Both Session ID and User ID:

    • Use a combination of session ID and user ID as the key to store and retrieve messages.
  2. Update the ChatMemoryBuffer to Use the Combined Key:

    • Ensure that the ChatMemoryBuffer uses the combined key to manage chat history.

Step 1: Initialize Redis Chat Store with Combined Key

import redis
from llama_index.legacy.storage.chat_store.redis_chat_store import RedisChatStore
from llama_index.legacy.memory.chat_memory_buffer import ChatMemoryBuffer

# Initialize Redis client with SSL and other parameters
redis_client = redis.StrictRedis.from_url(
    url,
    ssl=True,
    ssl_cert_reqs=None,  # Adjust as needed
    # Add other parameters as required
)

# Create RedisChatStore with the Redis client
chat_store = RedisChatStore(redis_client=redis_client, ttl=300)

Step 2: Create ChatMemoryBuffer with Combined Key

# Example session ID and user ID
session_id = "unique_session_id"
user_id = "user1"

# Create a combined key using session ID and user ID
combined_key = f"{session_id}:{user_id}"

# Create ChatMemoryBuffer with the Redis chat store and combined key
chat_memory = ChatMemoryBuffer.from_defaults(
    token_limit=3900,
    chat_store=chat_store,
    chat_store_key=combined_key,  # Use combined key as the chat store key
)

Step 3: Create and Use the Chat Engine

# Create the chat engine with the necessary parameters
chat_engine = index.as_chat_engine(
    chat_mode="condense_plus_context",
    memory=chat_memory,
    llm=llm,
    context_prompt=ContextPrompt,
    condense_prompt=CondensePrompt,
    streaming=True,
    verbose=True,
)

# Chat with the engine using the combined key
response = chat_engine.chat("Hello, how are you?")
print(response.response)

# Reset the session if needed
chat_store.delete_messages(combined_key)

In this setup:

This approach ensures that both the session ID and user ID are taken into account, maintaining both chronology and concurrency in your chatbot [1][2].

To continue talking to Dosu, mention @dosu.

avisheka93 commented 1 month ago

@dosu url = f"redis://:{REDIS_PASSWORD}@{REDIS_HOST}:6379" def create_key(): session_id = str(shortuuid.uuid()) user_id = str(shortuuid.uuid()) key=f"{session_id}__{user_id}" return key

chat_store = RedisChatStore(redis_url=url, ttl=300)

chat_memory = ChatMemoryBuffer.from_defaults( token_limit=3900, chat_store=chat_store, chat_store_key=create_key() )

the chatbot is working and i can have a conversation but i cant see any writes in azure redis its not giving any errors also...but i cant see anything getting written in azure redis as well if i do chat_store.get_keys() i see the keys created, in the chat_store.get_messages(key) i see conversation but nothing in azure redis

dosubot[bot] commented 1 month ago

To troubleshoot the issue of data not being visible in Azure Redis and ensure that it is being written correctly, you can follow these steps:

  1. Verify Data Insertion: Ensure that the data is being inserted correctly into the Redis store. You can use the put method to insert data and then immediately retrieve it using the get method to verify.

    test_key = "test_key"
    test_blob = {"test_obj_key": "test_obj_val"}
    redis_kvstore.put(test_key, test_blob)
    blob = redis_kvstore.get(test_key)
    assert blob == test_blob
  2. Check for Data Deletion: Ensure that the data is not being inadvertently deleted. After inserting the data, you can delete it and check if it is indeed removed.

    test_key = "test_key"
    test_blob = {"test_obj_key": "test_obj_val"}
    redis_kvstore.put(test_key, test_blob)
    redis_kvstore.delete(test_key)
    blob = redis_kvstore.get(test_key)
    assert blob is None
  3. Retrieve All Data: If you are expecting multiple entries, you can use the get_all method to retrieve all data and verify the count.

    test_key = "test_key"
    test_blob = {"test_obj_key": "test_obj_val"}
    redis_kvstore.put(test_key, test_blob)
    test_key2 = "test_key_2"
    test_blob2 = {"test_obj_key2": "test_obj_val2"}
    redis_kvstore.put(test_key2, test_blob2)
    blob = redis_kvstore.get_all()
    assert len(blob) == 2
  4. Batch Insert and Verify: Use the put_all method to insert multiple entries at once and then verify each entry.

    test_key = "test_key"
    test_blob = {"test_obj_key": "test_obj_val"}
    test_key2 = "test_key2"
    test_blob2 = {"test_obj_key2": "test_obj_val2"}
    redis_kvstore.put_all([(test_key, test_blob), (test_key2, test_blob2)])
    blob = redis_kvstore.get(test_key)
    assert blob == test_blob
    blob = redis_kvstore.get(test_key2)
    assert blob == test_blob2
  5. Ensure Redis Client Connection: Verify that the Redis client is correctly initialized and connected to the Redis server. You can do this by checking the connection parameters and ensuring that the redis_client is properly set up.

    from redis import Redis
    redis_client = Redis.from_url("redis://127.0.0.1:6379")
    redis_kvstore = RedisKVStore(redis_client=redis_client)

By following these steps, you can ensure that data is being written correctly to Azure Redis and troubleshoot any issues related to data visibility [1][2].

To continue talking to Dosu, mention @dosu.

avisheka93 commented 1 month ago

@dosu but here we are using Redischatstore to enable the storage of chat history chat_store = RedisChatStore(redis_url=url, ttl=300)

should the redis kv store be used to store chat conversations?

dosubot[bot] commented 1 month ago

You should use RedisChatStore to store chat conversations if you want to store your chat history remotely in Redis. This approach eliminates the need to manually persist and load the chat history.

Here is how you can set it up to ensure that chat history is correctly stored and visible in Azure Redis:

from llama_index.storage.chat_store.redis import RedisChatStore
from llama_index.core.memory import ChatMemoryBuffer

# Initialize RedisChatStore with your Redis URL
chat_store = RedisChatStore(redis_url="redis://your-azure-redis-url:6379", ttl=300)

# Create a ChatMemoryBuffer with the RedisChatStore
chat_memory = ChatMemoryBuffer.from_defaults(
    token_limit=3000,
    chat_store=chat_store,
    chat_store_key="user1",
)

This setup will ensure that your chat history is stored in Azure Redis and can be retrieved as needed. The RedisChatStore class provides methods to set, get, add, and delete messages, ensuring that your chat data is managed efficiently. The ttl parameter sets the time-to-live for the stored messages, which can help manage memory usage in Redis.

The RedisChatStore class includes methods such as set_messages, get_messages, add_message, delete_messages, delete_message, delete_last_message, and get_keys to handle various operations on chat messages, ensuring efficient management of chat data within your Redis instance [1][2][3].

To continue talking to Dosu, mention @dosu.

avisheka93 commented 1 month ago

@dosu how to manually persist and load the chat history in a remote db pls show

dosubot[bot] commented 1 month ago

To manually persist and load the chat history in a remote database for your enterprise-grade chatbot using the CondensePlusContextChatEngine from LlamaIndex, Python, and Redis for chat conversation memory, you can use the RedisChatStore. Here is how you can do it:

  1. Set up the RedisChatStore:

    from llama_index.storage.chat_store.redis import RedisChatStore
    from llama_index.legacy.memory import ChatMemoryBuffer
    
    # Initialize RedisChatStore with your Redis URL
    chat_store = RedisChatStore(redis_url="redis://your-azure-redis-url:6379", ttl=300)
    
    # Create a ChatMemoryBuffer with the RedisChatStore
    chat_memory = ChatMemoryBuffer.from_defaults(
       token_limit=3000,
       chat_store=chat_store,
       chat_store_key="user1",
    )
  2. Integrate with CondensePlusContextChatEngine:

    from llama_index.core.chat_engine.condense_plus_context import CondensePlusContextChatEngine
    from llama_index.legacy.core.base_retriever import BaseRetriever
    from llama_index.legacy.llms.llm import LLM
    
    # Assuming you have a retriever, LLM, and other necessary components set up
    retriever = BaseRetriever()  # Replace with actual retriever initialization
    llm = LLM()  # Replace with actual LLM initialization
    
    chat_engine = CondensePlusContextChatEngine(
       retriever=retriever,
       llm=llm,
       memory=chat_memory,
       context_prompt=None,
       condense_prompt=None,
       system_prompt=None,
       skip_condense=False,
       node_postprocessors=None,
       callback_manager=None,
       verbose=False,
    )
  3. Persist and Load Chat History: Since RedisChatStore handles the persistence automatically, you don't need to manually save and load the chat history. The chat history will be stored in Redis and can be retrieved as needed.

Here is the complete code snippet:

from llama_index.storage.chat_store.redis import RedisChatStore
from llama_index.legacy.memory import ChatMemoryBuffer
from llama_index.core.chat_engine.condense_plus_context import CondensePlusContextChatEngine
from llama_index.legacy.core.base_retriever import BaseRetriever
from llama_index.legacy.llms.llm import LLM

# Initialize RedisChatStore with your Redis URL
chat_store = RedisChatStore(redis_url="redis://your-azure-redis-url:6379", ttl=300)

# Create a ChatMemoryBuffer with the RedisChatStore
chat_memory = ChatMemoryBuffer.from_defaults(
    token_limit=3000,
    chat_store=chat_store,
    chat_store_key="user1",
)

# Initialize the CondensePlusContextChatEngine with the chat memory
retriever = BaseRetriever()  # Replace with actual retriever initialization
llm = LLM()  # Replace with actual LLM initialization

chat_engine = CondensePlusContextChatEngine(
    retriever=retriever,
    llm=llm,
    memory=chat_memory,
    context_prompt=None,
    condense_prompt=None,
    system_prompt=None,
    skip_condense=False,
    node_postprocessors=None,
    callback_manager=None,
    verbose=False,
)

# Example usage
response = chat_engine.chat("Hello.")
print(response)

This setup ensures that your chat history is automatically persisted in Redis and can be loaded as needed without manual intervention [1][2][3][4][5].

To continue talking to Dosu, mention @dosu.

avisheka93 commented 1 month ago

@dosu what if i want to integrate redis as chat memory without using RedisChatStore but native way ?

dosubot[bot] commented 1 month ago

To manually persist and load the chat history in Redis without using RedisChatStore, you can use the redis Python library to handle the storage and retrieval of chat messages. Here is an example of how you can achieve this:

  1. Install the Redis library:

    pip install redis
  2. Define functions to save and load chat history:

    import redis
    import json
    from llama_index.core.llms import ChatMessage
    from llama_index.core.memory import ChatMemoryBuffer
    
    # Initialize Redis client
    redis_client = redis.StrictRedis(host='your-redis-host', port=6379, db=0, ssl=True, ssl_cert_reqs=None)
    
    def _message_to_dict(message: ChatMessage) -> dict:
       return message.dict()
    
    def _dict_to_message(d: dict) -> ChatMessage:
       return ChatMessage.parse_obj(d)
    
    def save_chat_history(session_id, user_id, messages):
       key = f"chat:{session_id}:{user_id}"
       redis_client.delete(key)
       for message in messages:
           redis_client.rpush(key, json.dumps(_message_to_dict(message)))
    
    def load_chat_history(session_id, user_id):
       key = f"chat:{session_id}:{user_id}"
       items = redis_client.lrange(key, 0, -1)
       if not items:
           return []
       return [_dict_to_message(json.loads(item.decode("utf-8"))) for item in items]
    
    # Example usage
    session_id = "session123"
    user_id = "user456"
    messages = [
       ChatMessage(role="user", content="Hello!"),
       ChatMessage(role="bot", content="Hi there! How can I help you today?")
    ]
    
    # Save chat history
    save_chat_history(session_id, user_id, messages)
    
    # Load chat history
    loaded_messages = load_chat_history(session_id, user_id)
    print(loaded_messages)
  3. Integrate with CondensePlusContextChatEngine:

    from llama_index.core.chat_engine import CondensePlusContextChatEngine
    
    # Load chat history for a specific session and user
    loaded_messages = load_chat_history(session_id, user_id)
    
    # Create a ChatMemoryBuffer with the loaded messages
    chat_memory = ChatMemoryBuffer.from_defaults(
       token_limit=3000,
       chat_store=None,  # Not using a predefined chat store
       chat_store_key=None
    )
    chat_memory.messages = loaded_messages
    
    # Initialize the chat engine with the memory buffer
    chat_engine = CondensePlusContextChatEngine(memory=chat_memory)
    
    # Example chat interaction
    response = chat_engine.chat("Hello.")
    print(response)
    
    # Save updated chat history
    updated_messages = chat_memory.messages
    save_chat_history(session_id, user_id, updated_messages)

This approach ensures that the chat history is preserved in chronological order and manages concurrent sessions using both session ID and user ID [1][2][3].

To continue talking to Dosu, mention @dosu.

mohilmakwana3107 commented 2 weeks ago

@dosu How can I integrate RedisChatStore in query engine instead of chat engine?