File size: 7,780 Bytes
719dfe3
241f177
719dfe3
 
241f177
 
 
 
 
 
719dfe3
 
 
 
241f177
 
 
 
 
 
719dfe3
241f177
 
5e09d50
 
 
241f177
5e09d50
 
 
 
 
 
 
241f177
5e09d50
241f177
 
5e09d50
241f177
5e09d50
 
241f177
 
 
 
 
 
 
 
 
 
 
 
5e09d50
241f177
5e09d50
241f177
719dfe3
241f177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e3030c3
 
719dfe3
 
241f177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e3030c3
241f177
 
 
 
 
 
 
 
 
 
 
 
 
e3030c3
 
241f177
e3030c3
 
 
 
 
 
241f177
e3030c3
241f177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d1b9872
db781d4
241f177
719dfe3
 
 
241f177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from langgraph.graph import END
from langgraph.prebuilt import ToolNode
from langchain.memory import ConversationBufferMemory
from langchain_core.runnables.config import RunnableConfig
from langgraph.store.base import BaseStore
from typing import Literal
# from chainlit.logger import logger


from agent.utils.tools import tool_belt
from agent.utils.state import AgentState

# Initialize LLM for memory operations
model = ChatOpenAI(model="gpt-4", temperature=0)

# Define system prompt with memory
SYSTEM_PROMPT = """You are a Chief Joy Officer, an AI assistant focused on helping people find fun and enriching activities in London.
You have access to memory about the user's preferences and past interactions.

Here is what you remember about this user:
{memory}

Your core objectives are to:
1. Understand and remember user preferences and interests
2. Provide personalized activity recommendations based on their interests
3. Be engaging and enthusiastic while maintaining professionalism
4. Give clear, actionable suggestions

Key tools at your disposal:
- retrieve_context: For finding specific information about events and activities
- tavily_search: For general web searches about London activities

Always aim to provide value while being mindful of the user's time and interests."""

# Define memory creation/update prompt
MEMORY_UPDATE_PROMPT = """You are analyzing the conversation to update the user's profile and preferences.

CURRENT USER INFORMATION:
{memory}

INSTRUCTIONS:
1. Review the chat history carefully
2. Identify new information about the user, such as:
   - Activity preferences (indoor/outdoor, cultural/sports, etc.)
   - Specific interests (art, music, food, etc.)
   - Location preferences in London
   - Time/schedule constraints
   - Past experiences with activities
   - Budget considerations
3. Merge new information with existing memory
4. Format as a clear, bulleted list
5. If new information conflicts with existing memory, keep the most recent

Remember: Only include factual information directly stated by the user. Do not make assumptions.

Based on the conversation, please update the user information:"""

def get_last_human_message(state: AgentState):
    """Get the last human message from the state."""
    for message in reversed(state["messages"]):
        if isinstance(message, HumanMessage):
            return message
    return None

def call_model(state: AgentState, config: RunnableConfig, store: BaseStore):
    """Process messages using memory from the store."""
    # Get the user ID from the config
    user_id = config["configurable"].get("session_id", "default")
    
    # Retrieve memory from the store
    namespace = ("memory", user_id)
    existing_memory = store.get(namespace, "user_memory")
    
    # Extract memory content or use default
    memory_content = existing_memory.value.get('memory') if existing_memory else "No previous information about this user."
    
    # Create messages list with system prompt including memory
    messages = [
        SystemMessage(content=SYSTEM_PROMPT.format(memory=memory_content))
    ] + state["messages"]
    tool_calling_model = model.bind_tools(tool_belt)
    response = tool_calling_model.invoke(messages)
    return {"messages": [response]}

def update_memory(state: AgentState, config: RunnableConfig, store: BaseStore):
    """Update user memory based on conversation."""
    user_id = config["configurable"].get("session_id", "default")
    namespace = ("memory", user_id)
    existing_memory = store.get(namespace, "user_memory")
    
    memory_content = existing_memory.value.get('memory') if existing_memory else "No previous information about this user."
    
    update_prompt = MEMORY_UPDATE_PROMPT.format(memory=memory_content)
    new_memory = model.invoke([
        SystemMessage(content=update_prompt)
    ] + state["messages"])
    
    store.put(namespace, "user_memory", {"memory": new_memory.content})
    return state

def should_continue(state: AgentState) -> Literal["action", "write_memory"]:
    """Determine the next node in the graph."""
    if not state["messages"]:
        return END
        
    last_message = state["messages"][-1]
    if isinstance(last_message, list):
        last_message = last_message[-1]
        
    last_human_message = get_last_human_message(state)

    # Handle tool calls
    if hasattr(last_message, "additional_kwargs") and last_message.additional_kwargs.get("tool_calls"):
        return "action"

    return "write_memory"
    
    # # Handle memory operations for human messages
    # if last_human_message:
           
    #     # Write memory for longer messages that might contain personal information
    #     if len(last_human_message.content.split()) > 3:
    #         return "write_memory"
            
    # return END

# Define the memory creation prompt
MEMORY_CREATION_PROMPT = """"You are collecting information about the user to personalize your responses.

CURRENT USER INFORMATION:
{memory}

INSTRUCTIONS:
1. Review the chat history below carefully
2. Identify new information about the user, such as:
   - Personal details (name, location)
   - Preferences (likes, dislikes)
   - Interests and hobbies
   - Past experiences
   - Goals or future plans
3. Merge any new information with existing memory
4. Format the memory as a clear, bulleted list
5. If new information conflicts with existing memory, keep the most recent version

Remember: Only include factual information directly stated by the user. Do not make assumptions or inferences.

Based on the chat history below, please update the user information:"""

async def write_memory(state: AgentState, config: RunnableConfig, store: BaseStore) -> AgentState:
    """Reflect on the chat history and save a memory to the store."""

    # Get the session ID from config
    session_id = config["configurable"].get("session_id", "default")
    
    # Define the namespace for this user's memory
    namespace = ("memory", session_id)
    
    # Get existing memory using async interface
    existing_memory = await store.aget(namespace, "user_memory")
    memory_content = existing_memory.value.get('memory') if existing_memory else "No previous information about this user."
    
    # Create system message with memory context
    system_msg = SystemMessage(content=MEMORY_CREATION_PROMPT.format(memory=memory_content))
    
    # Get messages and ensure we're working with the correct format
    messages = state.get("messages", [])
    if not messages:
        return state
        
    # Create memory using the model
    new_memory = await model.ainvoke([system_msg] + messages)
    
    # Store the updated memory using async interface
    await store.aput(namespace, "user_memory", {"memory": new_memory.content})
    
    # Update the state with the new memory
    return state

# Initialize tool node
tool_node = ToolNode(tool_belt)

# def route_message(state: MessagesState, config: RunnableConfig, store: BaseStore) -> Literal[END, "update_todos", "update_instructions", "update_profile"]:
    
#     """Reflect on the memories and chat history to decide whether to update the memory collection."""
#     message = state['messages'][-1]
#     if len(message.tool_calls) ==0:
#         return END
#     else:
#         tool_call = message.tool_calls[0]
#         if tool_call['args']['update_type'] == "user":
#             return "update_profile"
#         elif tool_call['args']['update_type'] == "todo":
#             return "update_todos"
#         elif tool_call['args']['update_type'] == "instructions":
#             return "update_instructions"
#         else:
#             raise ValueError