0% found this document useful (0 votes)
9 views7 pages

Adding Memory to LLM Chat Systems

asdasdasdasdasdasda
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
9 views7 pages

Adding Memory to LLM Chat Systems

asdasdasdasdasdasda
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

📋 Complete Overview: Adding Memory to Your LLM Chat System

1. The Original Problem


Your original chat system had no memory. Each time a user sent a message, the LLM
treated it as a completely new conversation. It couldn't remember:

What the user asked before


What responses it gave
The context of the ongoing conversation

Example of the problem:


User: "Give me logs from /var/log/[Link]"
AI: [Provides logs]

User: "What was my previous question?"


AI: "This conversation just started" ❌ (Wrong!)

2. The Solution Architecture


I implemented a conversation memory system that:

Stores all previous messages (user questions + AI responses)


Retrieves this history when processing new queries
Includes the history in the prompt sent to the LLM
Maintains the history throughout the entire processing chain

3. Key Components Added


A. Memory Storage (Global Variable)
pythonconversation_history: List = []
MAX_HISTORY_LENGTH = 10
What it does:

conversation_history: A list that stores all conversation messages


MAX_HISTORY_LENGTH: Limits memory to last 10 exchanges (20 messages total: 10 user
+ 10 AI)

Why a list?

Simple to append new messages


Easy to retrieve recent history
Can be cleared when needed

Message Format:
We use LangChain's message objects:

HumanMessage(content="user's question") - For user messages


AIMessage(content="AI's response") - For AI responses

B. Helper Function: format_history_for_template()


pythondef format_history_for_template(history: List) -> str:
if not history:
return "This is the start of the conversation.\n\n"

history_text = "Previous conversation:\n"


for msg in history[-10:]:
role = "User" if isinstance(msg, HumanMessage) else "Assistant"
history_text += f"{role}: {[Link]}\n"
history_text += "\n"
return history_text
What it does:

Converts message objects into readable text


Formats it as "User: ... / Assistant: ..."
Takes only the last 10 messages to avoid overwhelming the LLM

Example output:
Previous conversation:
User: Give me logs from /var/log/[Link]
Assistant: I've searched for logs in /var/log/[Link]...
User: What was my previous question?

4. Modified Functions - Step by Step


Function 1: promptGenerator() - Entry Point
Original code:
pythonasync def promptGenerator(x: Dict[str, any]):
initialTemplate = formTemplateForModel()
prompt = [Link]({"user_input": x["user_input"]})
return {"prompt": prompt, "human": x["user_input"]}
Modified code:
pythonasync def promptGenerator(x: Dict[str, any]):
initialTemplate = formTemplateForModel()

# 1. Format history as text


history_text = ""
if conversation_history:
history_text = "Previous conversation:\n"
for msg in conversation_history[-10:]:
role = "User" if isinstance(msg, HumanMessage) else "Assistant"
history_text += f"{role}: {[Link]}\n"
history_text += "\n"
else:
history_text = "This is the start of the conversation.\n\n"

# 2. Debug output
print(f"\n📝 Current conversation history length: {len(conversation_history)}")

# 3. Create prompt with history


prompt_input = {
"user_input": x["user_input"],
"history_text": history_text # ← New: Include history
}
prompt = [Link](prompt_input)

# 4. Store current user message AFTER creating prompt


conversation_history.append(HumanMessage(content=x["user_input"]))

return {"prompt": prompt, "human": x["user_input"], "history":


conversation_history.copy()}
Key changes:

Format history as text before creating the prompt


Pass history to the template via history_text
Store user message in memory AFTER generating prompt (to avoid duplication)
Return history to pass it down the chain

Why store AFTER generating prompt?


If we store before, the current message would appear twice in the prompt
We want history to contain only PREVIOUS messages, not the current one

Function 2: processUserQuery() - LLM Invocation


Original code:
pythonasync def processUserQuery(x: Dict[str, any]):
results = await [Link](x["prompt"])
return {"results": results, "userQuery": x["human"]}
Modified code:
pythonasync def processUserQuery(x: Dict[str, any]):
prompt_with_history = x["prompt"]

print(f"\n🤖 Sending to LLM with {len([Link]('history', []))} history messages")

results = await [Link](prompt_with_history)

return {"results": results, "userQuery": x["human"], "history":


[Link]("history", [])}
Key changes:

Debug output to show how many history messages are being sent
Pass history forward in the return statement

Why pass history forward?

The next functions in the chain need access to history


This maintains history throughout the entire pipeline

Function 3: tool_call() - Tool Execution


Modified to include history in all return statements:
pythonasync def tool_call(x: Dict[str, Any]):
results = x["results"]
user_query = x["userQuery"]
history = [Link]("history", []) # ← Get history from input

tool_calls = results.additional_kwargs.get("tool_calls", [])


context = [Link]

if not tool_calls:
return {
"toolOutput": None,
"userQuery": user_query,
"context": context,
"history": history # ← Pass history forward
}

# ... tool execution code ...

return {
"toolOutput": tool_output,
"userQuery": user_query,
"context": context,
"history": history # ← Pass history forward
}
Key change:

Preserve history in all return paths so it reaches the next template


Function 4: analyzeQuery() - Final Response
Original code:
pythonasync def analyzeQuery(x: Dict[str, any]):
results = await [Link](x)
print([Link])
Modified code:
pythonasync def analyzeQuery(x: Dict[str, any]):
results = await [Link](x)
print([Link])

# Store AI response in conversation history


conversation_history.append(AIMessage(content=[Link]))

# Trim history if too long


if len(conversation_history) > MAX_HISTORY_LENGTH * 2:
conversation_history[:] = conversation_history[-(MAX_HISTORY_LENGTH * 2):]

return results
Key changes:

Store AI response in memory after generating it


Trim old messages to prevent memory overflow

Why trim?

LLMs have token limits (context window)


Keeping too much history can cause errors or slow responses
We keep only the most recent 10 exchanges (20 messages)

5. Template Modifications
A. formTemplateForModel() in [Link]
Original:
pythondef formTemplateForModel():
template = ChatPromptTemplate.from_messages([
("system", "You are a helpful SOC Analyst bot..."),
("ai", "Okay, can you clarify your requirements?"),
("system", "Your requirement is to analyze..."),
("human", "{user_input}")
])
return template
Modified:
pythondef formTemplateForModel():
template = ChatPromptTemplate.from_messages([
("system",
"You are a helpful SOC Analyst bot. You will provide analysis data over
the Splunk SIEM.\n\n"
"{history_text}" # ← New: Include history here
),
("ai", "Okay, can you clarify your requirements?"),
("system", "Your requirement is to analyze..."),
("human", "{user_input}")
])
return template
Key change:

Added {history_text} placeholder in the system message


This is where the formatted conversation history gets inserted

B. formParserTemplate() in [Link]
Modified similarly:
pythondef formParserTemplate():
template = ChatPromptTemplate.from_messages([
(
"system",
(
"You are a security operations analyst assistant..."
"7. Consider the conversation history to provide contextually
relevant responses.\n\n"
"{history_text}" # ← New: Include history here
)
),
(
"human",
"User Query: {userQuery}\n..."
),
])
return template
Why modify both templates?

First template: Used for initial query processing


Second template: Used for analyzing tool outputs
Both need access to conversation history for context

6. Chain Modification
Original chain:
pythonagent_chain = RunnableSequence(
RunnableLambda(promptGenerator),
RunnableLambda(processUserQuery),
RunnableLambda(tool_call),
secondTemplate,
RunnableLambda(analyzeQuery)
)
Modified chain:
pythonagent_chain = RunnableSequence(
RunnableLambda(promptGenerator),
RunnableLambda(processUserQuery),
RunnableLambda(tool_call),
RunnableLambda(lambda x: {
**x,
"history_text": format_history_for_template([Link]("history", []))
}) | secondTemplate, # ← Format history before second template
RunnableLambda(analyzeQuery)
)
Key change:

Added a lambda function before secondTemplate


Converts history from message objects to formatted text
Uses the spread operator **x to preserve all other data

7. User Commands Added


pythonif [Link]() == "clear":
conversation_history.clear()
print("🧹 Conversation history cleared!")
continue

if [Link]() == "history":
print("\n📜 Conversation History:")
if not conversation_history:
print(" (empty)")
for i, msg in enumerate(conversation_history):
role = "User" if isinstance(msg, HumanMessage) else "Assistant"
print(f" [{i+1}] {role}: {[Link][:100]}...")
continue
Commands:

clear: Wipes conversation memory (fresh start)


history: Shows all stored messages
quit: Exits the chat

8. Data Flow Diagram


User Query: "What was my previous question?"

[promptGenerator]
├─ Retrieves conversation_history
├─ Formats as text: "Previous conversation:\nUser: Give logs..."
├─ Creates prompt with history included
└─ Stores current query in history

[processUserQuery]
├─ Sends prompt (with history) to LLM
└─ Returns LLM response + history

[tool_call]
├─ Checks if tools needed
└─ Passes history forward

[Lambda + secondTemplate]
├─ Formats history again for second template
└─ Creates final prompt with history

[analyzeQuery]
├─ Gets final LLM response
├─ Stores AI response in conversation_history
└─ Prints response to user

9. Why This Approach Works


Initial Attempt (Failed):
I first tried using MessagesPlaceholder:
pythonMessagesPlaceholder(variable_name="history", optional=True)
Why it failed:

Some LLM providers (like Groq) don't fully support MessagesPlaceholder


The history wasn't being properly serialized into the prompt

Final Approach (Success):


Convert history to plain text and inject it directly into system prompt:
python"{history_text}"
Why this works:

✅ Works with ALL LLM providers


✅ Explicit and predictable
✅ Easy to debug (you can see exactly what's sent)
✅ No dependency on special LangChain features

10. Example Flow


Conversation:
User: "Give me logs from /var/log/[Link]"
AI: [Executes Splunk search and returns logs]

User: "What was my previous question?"


What happens internally:

Storage after first query:

pythonconversation_history = [
HumanMessage(content="Give me logs from /var/log/[Link]"),
AIMessage(content="I've executed a Splunk search...")
]

When second query arrives:

pythonhistory_text = """Previous conversation:


User: Give me logs from /var/log/[Link]
Assistant: I've executed a Splunk search...

"""

Prompt sent to LLM:

System: You are a helpful SOC Analyst bot...

Previous conversation:
User: Give me logs from /var/log/[Link]
Assistant: I've executed a Splunk search...RetryClaude does not have the ability to
run the code it generates yet.

You might also like