Storing Conversation History

Langchain includes functionality to integrate directly with Neo4j, including allowing you to run Cypher statements, query vector indexes and use Neo4j as a conversation memory store.

In this lesson, you will learn how to connect to and use a Neo4j database as a conversation memory store.

Storing conversation history in a Neo4j database allows you to analyze the conversation history to understand trends and improve outcomes.

Connecting to a Neo4j instance

The following code will connect to a Neo4j database and run a simple query.

python
from langchain_community.graphs import Neo4jGraph

graph = Neo4jGraph(
    url="bolt://localhost:7687",
    username="neo4j",
    password="pleaseletmein"
)

result = graph.query("""
MATCH (m:Movie{title: 'Toy Story'}) 
RETURN m.title, m.plot, m.poster
""")

print(result)

You can connect to the Neo4j sandbox created for you when you joined the course.

Update the code above to use the url, username and password of your Neo4j sandbox.

Connection URL

bolt://{sandbox-ip}:{sandbox-boltPort}

Username

{sandbox-username}

Password

{sandbox-password}

Run the query - you should see data about the movie Toy Story.

The Neo4jGraph class is a wrapper to the Neo4j Python driver. It simplifies connecting to Neo4j and integrating with the Langchain framework.

Schema

When you connect to the Neo4j database, the object loads the database schema into memory - this enables Langchain to access the schema information without having to query the database.

You can access the schema information using the schema property.

python
print(graph.schema)

Refreshing the schema

You can refresh the schema by calling the graph.refresh_schema() method.

Conversation History

In the previous lesson, you created a program that used the ChatMessageHistory component to store conversation history in memory.

You will now update this program to store the conversation history in your Neo4j sandbox using the Neo4jChatMessageHistory component.

Reveal the code
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.schema import StrOutputParser
from langchain_community.chat_message_histories import ChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

chat_llm = ChatOpenAI(openai_api_key="sk-...")

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are a surfer dude, having a conversation about the surf conditions on the beach. Respond using surfer slang.",
        ),
        ("system", "{context}"),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{question}"),
    ]
)

memory = ChatMessageHistory()

def get_memory(session_id):
    return memory

chat_chain = prompt | chat_llm | StrOutputParser()

chat_with_message_history = RunnableWithMessageHistory(
    chat_chain,
    get_memory,
    input_messages_key="question",
    history_messages_key="chat_history",
)


current_weather = """
    {
        "surf": [
            {"beach": "Fistral", "conditions": "6ft waves and offshore winds"},
            {"beach": "Bells", "conditions": "Flat and calm"},
            {"beach": "Watergate Bay", "conditions": "3ft waves and onshore winds"}
        ]
    }"""

while True:
    question = input("> ")

    response = chat_with_message_history.invoke(
        {
            "context": current_weather,
            "question": question,
            
        }, 
        config={
            "configurable": {"session_id": "none"}
        }
    )
    
    print(response)

Session ID

You must create and assign a session ID to each conversation to identify them.

The session ID can be any unique value, such as a Universally Unique Identifier (UUID).

You can generate a random UUID using the Python uuid.uuid4 function.

Create a new SESSION_ID constant in your chat model program.

python
from uuid import uuid4

SESSION_ID = str(uuid4())
print(f"Session ID: {SESSION_ID}")

This session ID will be used to identify the conversation in Neo4j.

Neo4j Chat Message History

Create a Neo4jGraph object to connect to your Neo4j sandbox.

python
from langchain_community.graphs import Neo4jGraph

graph = Neo4jGraph(
    url="bolt://localhost:7687",
    username="neo4j",
    password="pleaseletmein"
)

Remember to update the connection details with your Neo4j sandbox details.

Click to reveal your Sandbox connection details
Connection URL

bolt://{sandbox-ip}:{sandbox-boltPort}

Username

{sandbox-username}

Password

{sandbox-password}

Previously, the get_memory function returned an instance of ChatMessageHistory.

The get_memory function should now return an instance of Neo4jChatMessageHistory. You should pass the session_id and the graph connection you created as parameters.

python
from langchain_community.chat_message_histories import Neo4jChatMessageHistory

def get_memory(session_id):
    return Neo4jChatMessageHistory(session_id=session_id, graph=graph)

Finally, you must add the SESSION_ID to the config when you invoke the chat model.

python
    response = chat_with_message_history.invoke(
        {
            "context": current_weather,
            "question": question,
            
        }, 
        config={
            "configurable": {"session_id": SESSION_ID}
        }
    )
Click to reveal the complete code.
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.schema import StrOutputParser
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_community.graphs import Neo4jGraph
from langchain_community.chat_message_histories import Neo4jChatMessageHistory
from uuid import uuid4

SESSION_ID = str(uuid4())
print(f"Session ID: {SESSION_ID}")

chat_llm = ChatOpenAI(openai_api_key="sk-...")

graph = Neo4jGraph(
    url="bolt://localhost:7687",
    username="neo4j",
    password="pleaseletmein"
)

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "You are a surfer dude, having a conversation about the surf conditions on the beach. Respond using surfer slang.",
        ),
        ("system", "{context}"),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{question}"),
    ]
)

def get_memory(session_id):
    return Neo4jChatMessageHistory(session_id=session_id, graph=graph)

chat_chain = prompt | chat_llm | StrOutputParser()

chat_with_message_history = RunnableWithMessageHistory(
    chat_chain,
    get_memory,
    input_messages_key="question",
    history_messages_key="chat_history",
)

current_weather = """
    {
        "surf": [
            {"beach": "Fistral", "conditions": "6ft waves and offshore winds"},
            {"beach": "Bells", "conditions": "Flat and calm"},
            {"beach": "Watergate Bay", "conditions": "3ft waves and onshore winds"}
        ]
    }"""

while True:
    question = input("> ")

    response = chat_with_message_history.invoke(
        {
            "context": current_weather,
            "question": question,
            
        }, 
        config={
            "configurable": {"session_id": SESSION_ID}
        }
    )
    
    print(response)

Run the program and have a conversation with the chat model. The conversation history will now be stored in your Neo4j sandbox.

Conversation History Graph

The conversation history is stored using the following data model:

A graph data model showing 2 nodes Session and Message connected by a LAST_MESSAGE relationship. There is a circular NEXT relationship on the Message node.

The Session node represents a conversation session and has an id property.

The Message node represents a message in the conversation and has the following properties:

  • content - The message content

  • type - The message type: human, ai, or system

The LAST_MESSAGE relationship connects the Session node to the conversation’s last Message node. The NEXT relationship connects Message nodes in the conversation.

You can return the graph of the conversation history using the following Cypher query:

cypher
MATCH (s:Session)-[:LAST_MESSAGE]->(last:Message)<-[:NEXT*]-(msg:Message)
RETURN s, last, msg
A graph showing a Session node connected to a Message through with a LAST_MESSAGE relationship. Message nodes are connected to each other with NEXT relationships.

You can return the conversation history for a single session by filtering on the Session.id property.

cypher
MATCH (s:Session)-[:LAST_MESSAGE]->(last:Message)
WHERE s.id = 'your session id'
MATCH p = (last)<-[:NEXT*]-(msg:Message)
UNWIND nodes(p) as msgs
RETURN DISTINCT msgs.type, msgs.content

Check Your Understanding

Neo4j Langchain integration

Select all statements about the Langchain Neo4j integration that are True.

  • ✓ You can connect to a Neo4j database

  • ✓ The database schema is loaded automatically

  • ✓ You can run Cypher queries

  • ✓ Vectors can be queried

  • ✓ You can use Neo4j as a conversation memory store

Hint

The key goal is to simplify connecting to Neo4j and integrating with the Langchain framework.

Solution

All the statements are true.

Summary

In this lesson, you learned how to use a Neo4j database as a conversation memory store.

In the next lesson, you will learn how to create an agent to give an LLM access to different tools and data sources.