neorichi commited on
Commit
c0ff71f
·
verified ·
1 Parent(s): a958ae6

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,12 +1,6 @@
1
  ---
2
- title: Module05
3
- emoji: 🌖
4
- colorFrom: pink
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 5.46.1
8
- app_file: app.py
9
- pinned: false
10
  ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: module05
3
+ app_file: agentgradio.py
 
 
4
  sdk: gradio
5
+ sdk_version: 4.44.0
 
 
6
  ---
 
 
agentefinal.png ADDED
agentefinal.py ADDED
@@ -0,0 +1,463 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ from langchain_openai import ChatOpenAI
3
+ from typing import TypedDict, Literal
4
+ import uuid
5
+ from IPython.display import Image, display
6
+
7
+ from datetime import datetime
8
+ from trustcall import create_extractor
9
+ from typing import Optional
10
+ from pydantic import BaseModel, Field
11
+
12
+ from langchain_core.runnables import RunnableConfig
13
+ from langchain_core.messages import merge_message_runs, HumanMessage, SystemMessage
14
+
15
+ from langgraph.checkpoint.memory import MemorySaver
16
+ from langgraph.graph import StateGraph, MessagesState, END, START
17
+ from langgraph.store.base import BaseStore
18
+ from langgraph.store.memory import InMemoryStore
19
+
20
+
21
+ load_dotenv()
22
+ model = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
23
+ # Update memory tool
24
+ class UpdateMemory(TypedDict):
25
+ """ Decision on what memory type to update """
26
+ update_type: Literal['user', 'todo', 'instructions']
27
+
28
+ # User profile schema
29
+ class Profile(BaseModel):
30
+ """This is the profile of the user you are chatting with"""
31
+ name: Optional[str] = Field(description="The user's name", default=None)
32
+ location: Optional[str] = Field(description="The user's location", default=None)
33
+ job: Optional[str] = Field(description="The user's job", default=None)
34
+ connections: list[str] = Field(
35
+ description="Personal connection of the user, such as family members, friends, or coworkers",
36
+ default_factory=list
37
+ )
38
+ interests: list[str] = Field(
39
+ description="Interests that the user has",
40
+ default_factory=list
41
+ )
42
+
43
+ # ToDo schema
44
+ class ToDo(BaseModel):
45
+ task: str = Field(description="The task to be completed.")
46
+ time_to_complete: Optional[int] = Field(description="Estimated time to complete the task (minutes).")
47
+ deadline: Optional[datetime] = Field(
48
+ description="When the task needs to be completed by (if applicable)",
49
+ default=None
50
+ )
51
+ solutions: list[str] = Field(
52
+ description="List of specific, actionable solutions (e.g., specific ideas, service providers, or concrete options relevant to completing the task)",
53
+ min_items=1,
54
+ default_factory=list
55
+ )
56
+ status: Literal["not started", "in progress", "done", "archived"] = Field(
57
+ description="Current status of the task",
58
+ default="not started"
59
+ )
60
+
61
+ # Create the Trustcall extractor for updating the user profile
62
+ profile_extractor = create_extractor(
63
+ model,
64
+ tools=[Profile],
65
+ tool_choice="Profile",
66
+ )
67
+
68
+ # Inspect the tool calls made by Trustcall
69
+ class Spy:
70
+ def __init__(self):
71
+ self.called_tools = []
72
+
73
+ def __call__(self, run):
74
+ # Collect information about the tool calls made by the extractor.
75
+ q = [run]
76
+ while q:
77
+ r = q.pop()
78
+ if r.child_runs:
79
+ q.extend(r.child_runs)
80
+ if r.run_type == "chat_model":
81
+ self.called_tools.append(
82
+ r.outputs["generations"][0][0]["message"]["kwargs"]["tool_calls"]
83
+ )
84
+
85
+ # Initialize the spy
86
+ spy = Spy()
87
+
88
+ def extract_tool_info(tool_calls, schema_name="Memory"):
89
+ """Extract information from tool calls for both patches and new memories.
90
+
91
+ Args:
92
+ tool_calls: List of tool calls from the model
93
+ schema_name: Name of the schema tool (e.g., "Memory", "ToDo", "Profile")
94
+ """
95
+
96
+ # Initialize list of changes
97
+ changes = []
98
+
99
+ for call_group in tool_calls:
100
+ for call in call_group:
101
+ if call['name'] == 'PatchDoc':
102
+ if call['args'].get('patches') and len(call['args']['patches']) > 0:
103
+ changes.append({
104
+ 'type': 'update',
105
+ 'doc_id': call['args']['json_doc_id'],
106
+ 'planned_edits': call['args']['planned_edits'],
107
+ 'value': call['args']['patches'][0]['value']
108
+ })
109
+ elif call['name'] == schema_name:
110
+ changes.append({
111
+ 'type': 'new',
112
+ 'value': call['args']
113
+ })
114
+
115
+ # Format results as a single string
116
+ result_parts = []
117
+ for change in changes:
118
+ if change['type'] == 'update':
119
+ result_parts.append(
120
+ f"Document {change['doc_id']} updated:\n"
121
+ f"Plan: {change['planned_edits']}\n"
122
+ f"Added content: {change['value']}"
123
+ )
124
+ else:
125
+ result_parts.append(
126
+ f"New {schema_name} created:\n"
127
+ f"Content: {change['value']}"
128
+ )
129
+
130
+ return "\n\n".join(result_parts)
131
+
132
+ # Inspect spy.called_tools to see exactly what happened during the extraction
133
+ schema_name = "Memory"
134
+ changes = extract_tool_info(spy.called_tools, schema_name)
135
+ print(changes)
136
+
137
+ # Chatbot instruction for choosing what to update and what tools to call
138
+ MODEL_SYSTEM_MESSAGE = """You are a helpful chatbot.
139
+
140
+ You are designed to be a companion to a user, helping them keep track of their ToDo list.
141
+
142
+ You have a long term memory which keeps track of three things:
143
+ 1. The user's profile (general information about them)
144
+ 2. The user's ToDo list
145
+ 3. General instructions for updating the ToDo list
146
+
147
+ Here is the current User Profile (may be empty if no information has been collected yet):
148
+ <user_profile>
149
+ {user_profile}
150
+ </user_profile>
151
+
152
+ Here is the current ToDo List (may be empty if no tasks have been added yet):
153
+ <todo>
154
+ {todo}
155
+ </todo>
156
+
157
+ Here are the current user-specified preferences for updating the ToDo list (may be empty if no preferences have been specified yet):
158
+ <instructions>
159
+ {instructions}
160
+ </instructions>
161
+
162
+ Here are your instructions for reasoning about the user's messages:
163
+
164
+ 1. Reason carefully about the user's messages as presented below.
165
+
166
+ 2. Decide whether any of the your long-term memory should be updated:
167
+ - If personal information was provided about the user, update the user's profile by calling UpdateMemory tool with type `user`
168
+ - If tasks are mentioned, update the ToDo list by calling UpdateMemory tool with type `todo`
169
+ - If the user has specified preferences for how to update the ToDo list, update the instructions by calling UpdateMemory tool with type `instructions`
170
+
171
+ 3. Tell the user that you have updated your memory, if appropriate:
172
+ - Do not tell the user you have updated the user's profile
173
+ - Tell the user them when you update the todo list
174
+ - Do not tell the user that you have updated instructions
175
+
176
+ 4. Err on the side of updating the todo list. No need to ask for explicit permission.
177
+
178
+ 5. Respond naturally to user user after a tool call was made to save memories, or if no tool call was made."""
179
+
180
+ # Trustcall instruction
181
+ TRUSTCALL_INSTRUCTION = """Reflect on following interaction.
182
+
183
+ Use the provided tools to retain any necessary memories about the user.
184
+
185
+ Use parallel tool calling to handle updates and insertions simultaneously.
186
+
187
+ System Time: {time}"""
188
+
189
+ # Instructions for updating the ToDo list
190
+ CREATE_INSTRUCTIONS = """Reflect on the following interaction.
191
+
192
+ Based on this interaction, update your instructions for how to update ToDo list items.
193
+
194
+ Use any feedback from the user to update how they like to have items added, etc.
195
+
196
+ Your current instructions are:
197
+
198
+ <current_instructions>
199
+ {current_instructions}
200
+ </current_instructions>"""
201
+
202
+ # Node definitions
203
+ def task_mAIstro(state: MessagesState, config: RunnableConfig, store: BaseStore):
204
+
205
+ """Load memories from the store and use them to personalize the chatbot's response."""
206
+
207
+ # Get the user ID from the config
208
+ user_id = config["configurable"]["user_id"]
209
+
210
+ # Retrieve profile memory from the store
211
+ namespace = ("profile", user_id)
212
+ memories = store.search(namespace)
213
+ if memories:
214
+ user_profile = memories[0].value
215
+ else:
216
+ user_profile = None
217
+
218
+ # Retrieve task memory from the store
219
+ namespace = ("todo", user_id)
220
+ memories = store.search(namespace)
221
+ todo = "\n".join(f"{mem.value}" for mem in memories)
222
+
223
+ # Retrieve custom instructions
224
+ namespace = ("instructions", user_id)
225
+ memories = store.search(namespace)
226
+ if memories:
227
+ instructions = memories[0].value
228
+ else:
229
+ instructions = ""
230
+
231
+ system_msg = MODEL_SYSTEM_MESSAGE.format(user_profile=user_profile, todo=todo, instructions=instructions)
232
+
233
+ # Respond using memory as well as the chat history
234
+ response = model.bind_tools([UpdateMemory], parallel_tool_calls=False).invoke([SystemMessage(content=system_msg)]+state["messages"])
235
+
236
+ return {"messages": [response]}
237
+
238
+ def update_profile(state: MessagesState, config: RunnableConfig, store: BaseStore):
239
+
240
+ """Reflect on the chat history and update the memory collection."""
241
+
242
+ # Get the user ID from the config
243
+ user_id = config["configurable"]["user_id"]
244
+
245
+ # Define the namespace for the memories
246
+ namespace = ("profile", user_id)
247
+
248
+ # Retrieve the most recent memories for context
249
+ existing_items = store.search(namespace)
250
+
251
+ # Format the existing memories for the Trustcall extractor
252
+ tool_name = "Profile"
253
+ existing_memories = ([(existing_item.key, tool_name, existing_item.value)
254
+ for existing_item in existing_items]
255
+ if existing_items
256
+ else None
257
+ )
258
+
259
+ # Merge the chat history and the instruction
260
+ TRUSTCALL_INSTRUCTION_FORMATTED=TRUSTCALL_INSTRUCTION.format(time=datetime.now().isoformat())
261
+ updated_messages=list(merge_message_runs(messages=[SystemMessage(content=TRUSTCALL_INSTRUCTION_FORMATTED)] + state["messages"][:-1]))
262
+
263
+ # Invoke the extractor
264
+ result = profile_extractor.invoke({"messages": updated_messages,
265
+ "existing": existing_memories})
266
+
267
+ # Save the memories from Trustcall to the store
268
+ for r, rmeta in zip(result["responses"], result["response_metadata"]):
269
+ store.put(namespace,
270
+ rmeta.get("json_doc_id", str(uuid.uuid4())),
271
+ r.model_dump(mode="json"),
272
+ )
273
+ tool_calls = state['messages'][-1].tool_calls
274
+ return {"messages": [{"role": "tool", "content": "updated profile", "tool_call_id":tool_calls[0]['id']}]}
275
+
276
+ def update_todos(state: MessagesState, config: RunnableConfig, store: BaseStore):
277
+
278
+ """Reflect on the chat history and update the memory collection."""
279
+
280
+ # Get the user ID from the config
281
+ user_id = config["configurable"]["user_id"]
282
+
283
+ # Define the namespace for the memories
284
+ namespace = ("todo", user_id)
285
+
286
+ # Retrieve the most recent memories for context
287
+ existing_items = store.search(namespace)
288
+
289
+ # Format the existing memories for the Trustcall extractor
290
+ tool_name = "ToDo"
291
+ existing_memories = ([(existing_item.key, tool_name, existing_item.value)
292
+ for existing_item in existing_items]
293
+ if existing_items
294
+ else None
295
+ )
296
+
297
+ # Merge the chat history and the instruction
298
+ TRUSTCALL_INSTRUCTION_FORMATTED=TRUSTCALL_INSTRUCTION.format(time=datetime.now().isoformat())
299
+ updated_messages=list(merge_message_runs(messages=[SystemMessage(content=TRUSTCALL_INSTRUCTION_FORMATTED)] + state["messages"][:-1]))
300
+
301
+ # Initialize the spy for visibility into the tool calls made by Trustcall
302
+ spy = Spy()
303
+
304
+ # Create the Trustcall extractor for updating the ToDo list
305
+ todo_extractor = create_extractor(
306
+ model,
307
+ tools=[ToDo],
308
+ tool_choice=tool_name,
309
+ enable_inserts=True
310
+ ).with_listeners(on_end=spy)
311
+
312
+ # Invoke the extractor
313
+ result = todo_extractor.invoke({"messages": updated_messages,
314
+ "existing": existing_memories})
315
+
316
+ # Save the memories from Trustcall to the store
317
+ for r, rmeta in zip(result["responses"], result["response_metadata"]):
318
+ store.put(namespace,
319
+ rmeta.get("json_doc_id", str(uuid.uuid4())),
320
+ r.model_dump(mode="json"),
321
+ )
322
+
323
+ # Respond to the tool call made in task_mAIstro, confirming the update
324
+ tool_calls = state['messages'][-1].tool_calls
325
+
326
+ # Extract the changes made by Trustcall and add the the ToolMessage returned to task_mAIstro
327
+ todo_update_msg = extract_tool_info(spy.called_tools, tool_name)
328
+ return {"messages": [{"role": "tool", "content": todo_update_msg, "tool_call_id":tool_calls[0]['id']}]}
329
+
330
+ def update_instructions(state: MessagesState, config: RunnableConfig, store: BaseStore):
331
+
332
+ """Reflect on the chat history and update the memory collection."""
333
+
334
+ # Get the user ID from the config
335
+ user_id = config["configurable"]["user_id"]
336
+
337
+ namespace = ("instructions", user_id)
338
+
339
+ existing_memory = store.get(namespace, "user_instructions")
340
+
341
+ # Format the memory in the system prompt
342
+ system_msg = CREATE_INSTRUCTIONS.format(current_instructions=existing_memory.value if existing_memory else None)
343
+ new_memory = model.invoke([SystemMessage(content=system_msg)]+state['messages'][:-1] + [HumanMessage(content="Please update the instructions based on the conversation")])
344
+
345
+ # Overwrite the existing memory in the store
346
+ key = "user_instructions"
347
+ store.put(namespace, key, {"memory": new_memory.content})
348
+ tool_calls = state['messages'][-1].tool_calls
349
+ return {"messages": [{"role": "tool", "content": "updated instructions", "tool_call_id":tool_calls[0]['id']}]}
350
+
351
+ # Conditional edge
352
+ def route_message(state: MessagesState, config: RunnableConfig, store: BaseStore) -> Literal[END, "update_todos", "update_instructions", "update_profile"]:
353
+
354
+ """Reflect on the memories and chat history to decide whether to update the memory collection."""
355
+ message = state['messages'][-1]
356
+ if len(message.tool_calls) ==0:
357
+ return END
358
+ else:
359
+ tool_call = message.tool_calls[0]
360
+ if tool_call['args']['update_type'] == "user":
361
+ return "update_profile"
362
+ elif tool_call['args']['update_type'] == "todo":
363
+ return "update_todos"
364
+ elif tool_call['args']['update_type'] == "instructions":
365
+ return "update_instructions"
366
+ else:
367
+ raise ValueError
368
+
369
+ # Create the graph + all nodes
370
+ builder = StateGraph(MessagesState)
371
+
372
+ # Define the flow of the memory extraction process
373
+ builder.add_node(task_mAIstro)
374
+ builder.add_node(update_todos)
375
+ builder.add_node(update_profile)
376
+ builder.add_node(update_instructions)
377
+ builder.add_edge(START, "task_mAIstro")
378
+ builder.add_conditional_edges("task_mAIstro", route_message)
379
+ builder.add_edge("update_todos", "task_mAIstro")
380
+ builder.add_edge("update_profile", "task_mAIstro")
381
+ builder.add_edge("update_instructions", "task_mAIstro")
382
+
383
+ # Store for long-term (across-thread) memory
384
+ across_thread_memory = InMemoryStore()
385
+
386
+ # Checkpointer for short-term (within-thread) memory
387
+ within_thread_memory = MemorySaver()
388
+
389
+ # We compile the graph with the checkpointer and store
390
+ graph = builder.compile(checkpointer=within_thread_memory, store=across_thread_memory)
391
+
392
+ with open("agentefinal.png", "wb") as f:
393
+ f.write(graph.get_graph().draw_mermaid_png())
394
+
395
+ # We supply a thread ID for short-term (within-thread) memory
396
+ # We supply a user ID for long-term (across-thread) memory
397
+ config = {"configurable": {"thread_id": "1", "user_id": "Lance"}}
398
+
399
+ # User input to create a profile memory
400
+ input_messages = [HumanMessage(content="My name is Lance. I live in SF with my wife. I have a 1 year old daughter.")]
401
+
402
+ print("------------------")
403
+ print("Mensaje: 1")
404
+ print("------------------")
405
+ # Run the graph
406
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
407
+ chunk["messages"][-1].pretty_print()
408
+
409
+ # User input for a ToDo
410
+ input_messages = [HumanMessage(content="My wife asked me to book swim lessons for the baby.")]
411
+
412
+ print("------------------")
413
+ print("Mensaje: 2")
414
+ print("------------------")
415
+ # Run the graph
416
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
417
+ chunk["messages"][-1].pretty_print()
418
+
419
+ # User input to update instructions for creating ToDos
420
+ input_messages = [HumanMessage(content="When creating or updating ToDo items, include specific local businesses / vendors.")]
421
+ print("------------------")
422
+ print("Mensaje: 3")
423
+ print("------------------")
424
+ # Run the graph
425
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
426
+ chunk["messages"][-1].pretty_print()
427
+
428
+
429
+ # Check for updated instructions
430
+ user_id = "Lance"
431
+ print("------------------")
432
+ print("Mensaje: 4 Actualización")
433
+ print("------------------")
434
+ # Search
435
+ for memory in across_thread_memory.search(("instructions", user_id)):
436
+ print(memory.value)
437
+
438
+ # User input for a ToDo
439
+ input_messages = [HumanMessage(content="I need to fix the jammed electric Yale lock on the door.")]
440
+ print("------------------")
441
+ print("Mensaje: 5")
442
+ print("------------------")
443
+ # Run the graph
444
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
445
+ chunk["messages"][-1].pretty_print()
446
+
447
+ # Namespace for the memory to save
448
+ user_id = "Lance"
449
+ print("------------------")
450
+ print("Mensaje: 6")
451
+ print("------------------")
452
+ # Search
453
+ for memory in across_thread_memory.search(("todo", user_id)):
454
+ print(memory.value)
455
+
456
+ # User input to update an existing ToDo
457
+ input_messages = [HumanMessage(content="For the swim lessons, I need to get that done by end of November.")]
458
+ print("------------------")
459
+ print("Mensaje: 7")
460
+ print("------------------")
461
+ # Run the graph
462
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
463
+ chunk["messages"][-1].pretty_print()
agentgradio.py ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # agentefinal_gradio.py
2
+ # ---------------------
3
+ # Interfaz web con Gradio para tu agente basado en LangGraph + Trustcall.
4
+ # Toma como base tu agentefinal.py y expone un Chatbot en localhost.
5
+
6
+ from dotenv import load_dotenv
7
+ from langchain_openai import ChatOpenAI
8
+ from typing import TypedDict, Literal
9
+ import uuid
10
+ from datetime import datetime
11
+ from typing import Optional
12
+
13
+ from pydantic import BaseModel, Field
14
+ from trustcall import create_extractor
15
+
16
+ from langchain_core.runnables import RunnableConfig
17
+ from langchain_core.messages import merge_message_runs, HumanMessage, SystemMessage
18
+
19
+ from langgraph.checkpoint.memory import MemorySaver
20
+ from langgraph.graph import StateGraph, MessagesState, END, START
21
+ from langgraph.store.base import BaseStore
22
+ from langgraph.store.memory import InMemoryStore
23
+
24
+ # --- NUEVO: Gradio
25
+ import gradio as gr
26
+
27
+ # ---------------------------------------------------------------------
28
+ # CARGA DE VARIABLES DE ENTORNO (por ejemplo, OPENAI_API_KEY desde .env)
29
+ # ---------------------------------------------------------------------
30
+ load_dotenv()
31
+
32
+ # ---------------------------------------------------------------------
33
+ # MODELO BASE
34
+ # ---------------------------------------------------------------------
35
+ # Puedes ajustar el modelo/temperatura si lo necesitas.
36
+ model = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
37
+
38
+ # ---------------------------------------------------------------------
39
+ # TOOLS / ESQUEMAS
40
+ # ---------------------------------------------------------------------
41
+ class UpdateMemory(TypedDict):
42
+ """ Decision on what memory type to update """
43
+ update_type: Literal['user', 'todo', 'instructions']
44
+
45
+ class Profile(BaseModel):
46
+ """This is the profile of the user you are chatting with"""
47
+ name: Optional[str] = Field(description="The user's name", default=None)
48
+ location: Optional[str] = Field(description="The user's location", default=None)
49
+ job: Optional[str] = Field(description="The user's job", default=None)
50
+ connections: list[str] = Field(
51
+ description="Personal connection of the user, such as family members, friends, or coworkers",
52
+ default_factory=list
53
+ )
54
+ interests: list[str] = Field(
55
+ description="Interests that the user has",
56
+ default_factory=list
57
+ )
58
+
59
+ class ToDo(BaseModel):
60
+ task: str = Field(description="The task to be completed.")
61
+ time_to_complete: Optional[int] = Field(description="Estimated time to complete the task (minutes).")
62
+ deadline: Optional[datetime] = Field(
63
+ description="When the task needs to be completed by (if applicable)",
64
+ default=None
65
+ )
66
+ solutions: list[str] = Field(
67
+ description="List of specific, actionable solutions (e.g., specific ideas, service providers, or concrete options relevant to completing the task)",
68
+ min_items=1,
69
+ default_factory=list
70
+ )
71
+ status: Literal["not started", "in progress", "done", "archived"] = Field(
72
+ description="Current status of the task",
73
+ default="not started"
74
+ )
75
+
76
+ # Extractor para perfil
77
+ profile_extractor = create_extractor(
78
+ model,
79
+ tools=[Profile],
80
+ tool_choice="Profile",
81
+ )
82
+
83
+ # ---------------------------------------------------------------------
84
+ # UTILIDAD PARA INSPECCIONAR LLAMADAS DE HERRAMIENTAS (Trustcall)
85
+ # ---------------------------------------------------------------------
86
+ class Spy:
87
+ def __init__(self):
88
+ self.called_tools = []
89
+
90
+ def __call__(self, run):
91
+ q = [run]
92
+ while q:
93
+ r = q.pop()
94
+ if getattr(r, "child_runs", None):
95
+ q.extend(r.child_runs)
96
+ if getattr(r, "run_type", None) == "chat_model":
97
+ try:
98
+ self.called_tools.append(
99
+ r.outputs["generations"][0][0]["message"]["kwargs"]["tool_calls"]
100
+ )
101
+ except Exception:
102
+ pass
103
+
104
+ def extract_tool_info(tool_calls, schema_name="Memory"):
105
+ """Extrae información útil de las tool calls (Trustcall)."""
106
+ changes = []
107
+ for call_group in tool_calls:
108
+ for call in call_group:
109
+ if call.get('name') == 'PatchDoc':
110
+ if call.get('args', {}).get('patches'):
111
+ changes.append({
112
+ 'type': 'update',
113
+ 'doc_id': call['args'].get('json_doc_id'),
114
+ 'planned_edits': call['args'].get('planned_edits'),
115
+ 'value': call['args']['patches'][0].get('value')
116
+ })
117
+ elif call.get('name') == schema_name:
118
+ changes.append({'type': 'new', 'value': call.get('args')})
119
+
120
+ result_parts = []
121
+ for change in changes:
122
+ if change['type'] == 'update':
123
+ result_parts.append(
124
+ f"Document {change['doc_id']} updated:\n"
125
+ f"Plan: {change['planned_edits']}\n"
126
+ f"Added content: {change['value']}"
127
+ )
128
+ else:
129
+ result_parts.append(
130
+ f"New {schema_name} created:\n"
131
+ f"Content: {change['value']}"
132
+ )
133
+ return "\n\n".join(result_parts)
134
+
135
+ # ---------------------------------------------------------------------
136
+ # PROMPTS DEL AGENTE
137
+ # ---------------------------------------------------------------------
138
+ MODEL_SYSTEM_MESSAGE = """You are a helpful chatbot.
139
+
140
+ You are designed to be a companion to a user, helping them keep track of their ToDo list.
141
+
142
+ You have a long term memory which keeps track of three things:
143
+ 1. The user's profile (general information about them)
144
+ 2. The user's ToDo list
145
+ 3. General instructions for updating the ToDo list
146
+
147
+ Here is the current User Profile (may be empty if no information has been collected yet):
148
+ <user_profile>
149
+ {user_profile}
150
+ </user_profile>
151
+
152
+ Here is the current ToDo List (may be empty if no tasks have been added yet):
153
+ <todo>
154
+ {todo}
155
+ </todo>
156
+
157
+ Here are the current user-specified preferences for updating the ToDo list (may be empty if no preferences have been specified yet):
158
+ <instructions>
159
+ {instructions}
160
+ </instructions>
161
+
162
+ Here are your instructions for reasoning about the user's messages:
163
+
164
+ 1. Reason carefully about the user's messages as presented below.
165
+
166
+ 2. Decide whether any of the your long-term memory should be updated:
167
+ - If personal information was provided about the user, update the user's profile by calling UpdateMemory tool with type `user`
168
+ - If tasks are mentioned, update the ToDo list by calling UpdateMemory tool with type `todo`
169
+ - If the user has specified preferences for how to update the ToDo list, update the instructions by calling UpdateMemory tool with type `instructions`
170
+
171
+ 3. Tell the user that you have updated your memory, if appropriate:
172
+ - Do not tell the user you have updated the user's profile
173
+ - Tell the user them when you update the todo list
174
+ - Do not tell the user that you have updated instructions
175
+
176
+ 4. Err on the side of updating the todo list. No need to ask for explicit permission.
177
+
178
+ 5. Respond naturally to user user after a tool call was made to save memories, or if no tool call was made."""
179
+
180
+ TRUSTCALL_INSTRUCTION = """Reflect on following interaction.
181
+
182
+ Use the provided tools to retain any necessary memories about the user.
183
+
184
+ Use parallel tool calling to handle updates and insertions simultaneously.
185
+
186
+ System Time: {time}"""
187
+
188
+ CREATE_INSTRUCTIONS = """Reflect on the following interaction.
189
+
190
+ Based on this interaction, update your instructions for how to update ToDo list items.
191
+
192
+ Use any feedback from the user to update how they like to have items added, etc.
193
+
194
+ Your current instructions are:
195
+
196
+ <current_instructions>
197
+ {current_instructions}
198
+ </current_instructions>"""
199
+
200
+ # ---------------------------------------------------------------------
201
+ # NODOS DEL GRAFO
202
+ # ---------------------------------------------------------------------
203
+ def task_mAIstro(state: MessagesState, config: RunnableConfig, store: BaseStore):
204
+ """Carga memorias y responde con el modelo, decidiendo si llamar UpdateMemory."""
205
+ user_id = config["configurable"]["user_id"]
206
+
207
+ # Profile
208
+ namespace = ("profile", user_id)
209
+ memories = store.search(namespace)
210
+ user_profile = memories[0].value if memories else None
211
+
212
+ # ToDo
213
+ namespace = ("todo", user_id)
214
+ memories = store.search(namespace)
215
+ todo = "\n".join(f"{mem.value}" for mem in memories)
216
+
217
+ # Instrucciones
218
+ namespace = ("instructions", user_id)
219
+ memories = store.search(namespace)
220
+ instructions = memories[0].value if memories else ""
221
+
222
+ system_msg = MODEL_SYSTEM_MESSAGE.format(
223
+ user_profile=user_profile,
224
+ todo=todo,
225
+ instructions=instructions
226
+ )
227
+
228
+ response = model.bind_tools([UpdateMemory], parallel_tool_calls=False).invoke(
229
+ [SystemMessage(content=system_msg)] + state["messages"]
230
+ )
231
+ return {"messages": [response]}
232
+
233
+ def update_profile(state: MessagesState, config: RunnableConfig, store: BaseStore):
234
+ """Actualiza memoria de perfil con Trustcall."""
235
+ user_id = config["configurable"]["user_id"]
236
+ namespace = ("profile", user_id)
237
+
238
+ existing_items = store.search(namespace)
239
+ tool_name = "Profile"
240
+ existing_memories = ([(existing_item.key, tool_name, existing_item.value)
241
+ for existing_item in existing_items] if existing_items else None)
242
+
243
+ TRUSTCALL_INSTRUCTION_FORMATTED = TRUSTCALL_INSTRUCTION.format(time=datetime.now().isoformat())
244
+ updated_messages = list(merge_message_runs(
245
+ messages=[SystemMessage(content=TRUSTCALL_INSTRUCTION_FORMATTED)] + state["messages"][:-1]
246
+ ))
247
+
248
+ result = profile_extractor.invoke({"messages": updated_messages, "existing": existing_memories})
249
+
250
+ for r, rmeta in zip(result["responses"], result["response_metadata"]):
251
+ store.put(namespace, rmeta.get("json_doc_id", str(uuid.uuid4())), r.model_dump(mode="json"))
252
+
253
+ tool_calls = state['messages'][-1].tool_calls
254
+ return {"messages": [{"role": "tool", "content": "updated profile", "tool_call_id": tool_calls[0]['id']}]}
255
+
256
+ def update_todos(state: MessagesState, config: RunnableConfig, store: BaseStore):
257
+ """Actualiza ToDos con Trustcall (inserciones + parches)."""
258
+ user_id = config["configurable"]["user_id"]
259
+ namespace = ("todo", user_id)
260
+
261
+ existing_items = store.search(namespace)
262
+ tool_name = "ToDo"
263
+ existing_memories = ([(existing_item.key, tool_name, existing_item.value)
264
+ for existing_item in existing_items] if existing_items else None)
265
+
266
+ TRUSTCALL_INSTRUCTION_FORMATTED = TRUSTCALL_INSTRUCTION.format(time=datetime.now().isoformat())
267
+ updated_messages = list(merge_message_runs(
268
+ messages=[SystemMessage(content=TRUSTCALL_INSTRUCTION_FORMATTED)] + state["messages"][:-1]
269
+ ))
270
+
271
+ spy = Spy()
272
+ todo_extractor = create_extractor(
273
+ model,
274
+ tools=[ToDo],
275
+ tool_choice=tool_name,
276
+ enable_inserts=True
277
+ ).with_listeners(on_end=spy)
278
+
279
+ result = todo_extractor.invoke({"messages": updated_messages, "existing": existing_memories})
280
+
281
+ for r, rmeta in zip(result["responses"], result["response_metadata"]):
282
+ store.put(namespace, rmeta.get("json_doc_id", str(uuid.uuid4())), r.model_dump(mode="json"))
283
+
284
+ tool_calls = state['messages'][-1].tool_calls
285
+ todo_update_msg = extract_tool_info(spy.called_tools, tool_name)
286
+ return {"messages": [{"role": "tool", "content": todo_update_msg or "updated todos", "tool_call_id": tool_calls[0]['id']}]}
287
+
288
+ def update_instructions(state: MessagesState, config: RunnableConfig, store: BaseStore):
289
+ """Actualiza instrucciones personalizadas del usuario."""
290
+ user_id = config["configurable"]["user_id"]
291
+ namespace = ("instructions", user_id)
292
+
293
+ existing_memory = store.get(namespace, "user_instructions")
294
+ system_msg = CREATE_INSTRUCTIONS.format(
295
+ current_instructions=existing_memory.value if existing_memory else None
296
+ )
297
+
298
+ new_memory = model.invoke(
299
+ [SystemMessage(content=system_msg)] +
300
+ state['messages'][:-1] +
301
+ [HumanMessage(content="Please update the instructions based on the conversation")]
302
+ )
303
+
304
+ store.put(namespace, "user_instructions", {"memory": new_memory.content})
305
+ tool_calls = state['messages'][-1].tool_calls
306
+ return {"messages": [{"role": "tool", "content": "updated instructions", "tool_call_id": tool_calls[0]['id']}]}
307
+
308
+ def route_message(state: MessagesState, config: RunnableConfig, store: BaseStore) -> Literal[END, "update_todos", "update_instructions", "update_profile"]:
309
+ """Decide qué colección actualizar según la tool call del modelo."""
310
+ message = state['messages'][-1]
311
+ if len(getattr(message, "tool_calls", []) or []) == 0:
312
+ return END
313
+ tool_call = message.tool_calls[0]
314
+ ut = tool_call['args']['update_type']
315
+ if ut == "user":
316
+ return "update_profile"
317
+ elif ut == "todo":
318
+ return "update_todos"
319
+ elif ut == "instructions":
320
+ return "update_instructions"
321
+ else:
322
+ raise ValueError("Unknown update_type")
323
+
324
+ # ---------------------------------------------------------------------
325
+ # COMPILACIÓN DEL GRAFO + MEMORIA
326
+ # ---------------------------------------------------------------------
327
+ def build_graph():
328
+ builder = StateGraph(MessagesState)
329
+ builder.add_node(task_mAIstro)
330
+ builder.add_node(update_todos)
331
+ builder.add_node(update_profile)
332
+ builder.add_node(update_instructions)
333
+
334
+ builder.add_edge(START, "task_mAIstro")
335
+ builder.add_conditional_edges("task_mAIstro", route_message)
336
+ builder.add_edge("update_todos", "task_mAIstro")
337
+ builder.add_edge("update_profile", "task_mAIstro")
338
+ builder.add_edge("update_instructions", "task_mAIstro")
339
+
340
+ across_thread_memory = InMemoryStore() # memoria largo plazo (en RAM)
341
+ within_thread_memory = MemorySaver() # checkpointing corto plazo
342
+
343
+ graph = builder.compile(checkpointer=within_thread_memory, store=across_thread_memory)
344
+ return graph, across_thread_memory, within_thread_memory
345
+
346
+ GRAPH, STORE, CHECKPOINTER = build_graph()
347
+
348
+ # ---------------------------------------------------------------------
349
+ # FUNCIÓN DE CHAT PARA GRADIO
350
+ # ---------------------------------------------------------------------
351
+ def chat_fn(user_input, history, user_id, thread_id):
352
+ """
353
+ - user_input: texto del usuario
354
+ - history: historial [(user, bot), ...] mostrado en Gradio
355
+ - user_id: id lógico para memoria a largo plazo (e.g., nombre)
356
+ - thread_id: id del hilo para memoria de corto plazo
357
+ """
358
+ # Config para LangGraph
359
+ config = {"configurable": {"thread_id": str(thread_id or "1"), "user_id": str(user_id or "default")}}
360
+ input_messages = [HumanMessage(content=user_input or "")]
361
+
362
+ # Ejecutar grafo por streaming y quedarnos con el último mensaje
363
+ response_text = ""
364
+ try:
365
+ for chunk in GRAPH.stream({"messages": input_messages}, config, stream_mode="values"):
366
+ msg = chunk["messages"][-1]
367
+ # msg puede ser un ChatMessage, ToolMessage, etc.
368
+ content = getattr(msg, "content", None)
369
+ if content:
370
+ response_text = content
371
+ except Exception as e:
372
+ response_text = f"Oops, hubo un error procesando tu mensaje: {e}"
373
+
374
+ # Actualizamos historial para el componente Chatbot
375
+ history = (history or []) + [(user_input, response_text)]
376
+ return history, history
377
+
378
+ def clear_fn():
379
+ return [], []
380
+
381
+ # ---------------------------------------------------------------------
382
+ # UI DE GRADIO
383
+ # ---------------------------------------------------------------------
384
+ def build_ui():
385
+ with gr.Blocks(title="Agente con Memoria (LangGraph + Trustcall)") as demo:
386
+ gr.Markdown("## 🧠 Agente ToDo con memoria (LangGraph + Trustcall) + Gradio")
387
+
388
+ with gr.Row():
389
+ user_id = gr.Textbox(label="User ID (memoria largo plazo)", value="Lance")
390
+ thread_id = gr.Textbox(label="Thread ID (memoria corto plazo)", value="1")
391
+
392
+ chatbot = gr.Chatbot(label="Chat")
393
+ msg = gr.Textbox(label="Escribe tu mensaje", placeholder="Hola, me llamo... Añade 'reservar clases...' etc.", lines=2)
394
+ with gr.Row():
395
+ send = gr.Button("Enviar", variant="primary")
396
+ clear = gr.Button("Limpiar historial")
397
+
398
+ state = gr.State([]) # historial
399
+
400
+ # Acciones
401
+ msg.submit(chat_fn, [msg, state, user_id, thread_id], [chatbot, state])
402
+ send.click(chat_fn, [msg, state, user_id, thread_id], [chatbot, state])
403
+ clear.click(lambda: ([], []), None, [chatbot, state])
404
+
405
+ gr.Markdown(
406
+ "Consejo: usa un **User ID** constante para que la memoria de perfil y ToDos "
407
+ "se mantenga entre mensajes. Cambia el **Thread ID** para conversaciones paralelas."
408
+ )
409
+ return demo
410
+
411
+ # ---------------------------------------------------------------------
412
+ # MAIN
413
+ # ---------------------------------------------------------------------
414
+ if __name__ == "__main__":
415
+ demo = build_ui()
416
+ demo.queue().launch(
417
+ share=True,
418
+ server_name="0.0.0.0",
419
+ server_port=7860,
420
+ show_error=True
421
+ )
memoryagent03.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ from langchain_openai import ChatOpenAI
3
+ from pydantic import BaseModel, Field
4
+ from trustcall import create_extractor
5
+ from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
6
+ from typing import TypedDict, Literal
7
+
8
+ load_dotenv()
9
+ model = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
10
+
11
+ class Memory(BaseModel):
12
+ content: str = Field(description="The main content of the memory. For example: User expressed interest in learning about French.")
13
+
14
+ class MemoryCollection(BaseModel):
15
+ memories: list[Memory] = Field(description="A list of memories about the user.")
16
+
17
+ # Inspect the tool calls made by Trustcall
18
+ class Spy:
19
+ def __init__(self):
20
+ self.called_tools = []
21
+
22
+ def __call__(self, run):
23
+ # Collect information about the tool calls made by the extractor.
24
+ q = [run]
25
+ while q:
26
+ r = q.pop()
27
+ if r.child_runs:
28
+ q.extend(r.child_runs)
29
+ if r.run_type == "chat_model":
30
+ self.called_tools.append(
31
+ r.outputs["generations"][0][0]["message"]["kwargs"]["tool_calls"]
32
+ )
33
+
34
+ # Initialize the spy
35
+ spy = Spy()
36
+
37
+ # Create the extractor
38
+ trustcall_extractor = create_extractor(
39
+ model,
40
+ tools=[Memory],
41
+ tool_choice="Memory",
42
+ enable_inserts=True,
43
+ )
44
+
45
+ # Add the spy as a listener
46
+ trustcall_extractor_see_all_tool_calls = trustcall_extractor.with_listeners(on_end=spy)
47
+
48
+ # Instruction
49
+ instruction = """Extract memories from the following conversation:"""
50
+
51
+ # Conversation
52
+ conversation = [HumanMessage(content="Hi, I'm Lance."),
53
+ AIMessage(content="Nice to meet you, Lance."),
54
+ HumanMessage(content="This morning I had a nice bike ride in San Francisco.")]
55
+
56
+ # Invoke the extractor
57
+ result = trustcall_extractor.invoke({"messages": [SystemMessage(content=instruction)] + conversation})
58
+
59
+ print("------------------")
60
+ print("Mensaje: 1")
61
+ print("------------------")
62
+ # Messages contain the tool calls
63
+ for m in result["messages"]:
64
+ m.pretty_print()
65
+
66
+ # Update the conversation
67
+ updated_conversation = [AIMessage(content="That's great, did you do after?"),
68
+ HumanMessage(content="I went to Tartine and ate a croissant."),
69
+ AIMessage(content="What else is on your mind?"),
70
+ HumanMessage(content="I was thinking about my Japan, and going back this winter!"),]
71
+
72
+
73
+ print("------------------")
74
+ print("Mensaje: 2: Update system message")
75
+ print("------------------")
76
+ # Update the instruction
77
+ system_msg = """Update existing memories and create new ones based on the following conversation:"""
78
+
79
+ # We'll save existing memories, giving them an ID, key (tool name), and value
80
+ tool_name = "Memory"
81
+ existing_memories = [(str(i), tool_name, memory.model_dump()) for i, memory in enumerate(result["responses"])] if result["responses"] else None
82
+ print(existing_memories)
83
+
84
+ # Invoke the extractor with our updated conversation and existing memories
85
+ result = trustcall_extractor_see_all_tool_calls.invoke({"messages": updated_conversation,
86
+ "existing": existing_memories})
87
+
88
+ print("------------------")
89
+ print("Mensaje: 3: metadata and tool calls")
90
+ print("------------------")
91
+ # Metadata contains the tool call
92
+ for m in result["response_metadata"]:
93
+ print(m)
94
+
95
+ print("------------------")
96
+ print("Mensaje: 4: metadata and tool calls")
97
+ print("------------------")
98
+ # Messages contain the tool calls
99
+ for m in result["messages"]:
100
+ m.pretty_print()
101
+
102
+ print("------------------")
103
+ print("Mensaje: 5: Parsed responses")
104
+ print("------------------")
105
+ # Parsed responses
106
+ for m in result["responses"]:
107
+ print(m)
108
+
109
+ print("------------------")
110
+ print("Mensaje: 6: Inspect the tool calls made by Trustcall")
111
+ print("------------------")
112
+ # Inspect the tool calls made by Trustcall
113
+ print(spy.called_tools)
114
+
115
+ def extract_tool_info(tool_calls, schema_name="Memory"):
116
+ """Extract information from tool calls for both patches and new memories.
117
+
118
+ Args:
119
+ tool_calls: List of tool calls from the model
120
+ schema_name: Name of the schema tool (e.g., "Memory", "ToDo", "Profile")
121
+ """
122
+
123
+ # Initialize list of changes
124
+ changes = []
125
+
126
+ for call_group in tool_calls:
127
+ for call in call_group:
128
+ if call['name'] == 'PatchDoc':
129
+ changes.append({
130
+ 'type': 'update',
131
+ 'doc_id': call['args']['json_doc_id'],
132
+ 'planned_edits': call['args']['planned_edits'],
133
+ 'value': call['args']['patches'][0]['value']
134
+ })
135
+ elif call['name'] == schema_name:
136
+ changes.append({
137
+ 'type': 'new',
138
+ 'value': call['args']
139
+ })
140
+
141
+ # Format results as a single string
142
+ result_parts = []
143
+ for change in changes:
144
+ if change['type'] == 'update':
145
+ result_parts.append(
146
+ f"Document {change['doc_id']} updated:\n"
147
+ f"Plan: {change['planned_edits']}\n"
148
+ f"Added content: {change['value']}"
149
+ )
150
+ else:
151
+ result_parts.append(
152
+ f"New {schema_name} created:\n"
153
+ f"Content: {change['value']}"
154
+ )
155
+
156
+ return "\n\n".join(result_parts)
157
+
158
+ print("------------------")
159
+ print("Mensaje: 7: Extracted changes")
160
+ print("------------------")
161
+ # Inspect spy.called_tools to see exactly what happened during the extraction
162
+ schema_name = "Memory"
163
+ changes = extract_tool_info(spy.called_tools, schema_name)
164
+ print(changes)
memoryschema01.png ADDED
memoryschema02.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ from langchain_openai import ChatOpenAI
3
+ from typing import TypedDict, List
4
+ import uuid
5
+ from langgraph.store.memory import InMemoryStore
6
+ from pydantic import BaseModel, Field
7
+
8
+ from langchain_core.messages import HumanMessage
9
+ from IPython.display import Image, display
10
+
11
+ from langgraph.checkpoint.memory import MemorySaver
12
+ from langgraph.graph import StateGraph, MessagesState, START, END
13
+ from langgraph.store.base import BaseStore
14
+
15
+ from langchain_core.messages import HumanMessage, SystemMessage, AIMessage
16
+ from langchain_core.runnables.config import RunnableConfig
17
+ from trustcall import create_extractor
18
+
19
+
20
+ load_dotenv()
21
+ model = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
22
+
23
+ from typing import TypedDict, List
24
+
25
+ class UserProfile(TypedDict):
26
+ """User profile schema with typed fields"""
27
+ user_name: str # The user's preferred name
28
+ interests: List[str] # A list of the user's interests
29
+
30
+ # TypedDict instance
31
+ user_profile: UserProfile = {
32
+ "user_name": "Lance",
33
+ "interests": ["biking", "technology", "coffee"]
34
+ }
35
+ print("------------------")
36
+ print("User Profile:")
37
+ print("------------------")
38
+ print(user_profile)
39
+
40
+ # Initialize the in-memory store
41
+ in_memory_store = InMemoryStore()
42
+
43
+ # Namespace for the memory to save
44
+ user_id = "1"
45
+ namespace_for_memory = (user_id, "memory")
46
+
47
+ # Save a memory to namespace as key and value
48
+ key = "user_profile"
49
+ value = user_profile
50
+ in_memory_store.put(namespace_for_memory, key, value)
51
+
52
+ # Search
53
+ for m in in_memory_store.search(namespace_for_memory):
54
+ print(m.dict())
55
+
56
+ # Get the memory by namespace and key
57
+ profile = in_memory_store.get(namespace_for_memory, "user_profile")
58
+ print (profile.value)
59
+
60
+ # Bind schema to model
61
+ model_with_structure = model.with_structured_output(UserProfile)
62
+
63
+ # Invoke the model to produce structured output that matches the schema
64
+ structured_output = model_with_structure.invoke([HumanMessage("My name is Lance, I like to bike.")])
65
+ print("------------------")
66
+ print("Structured Output:")
67
+ print("------------------")
68
+ print(structured_output)
69
+
70
+ # Chatbot instruction
71
+ MODEL_SYSTEM_MESSAGE = """You are a helpful assistant with memory that provides information about the user.
72
+ If you have memory for this user, use it to personalize your responses.
73
+ Here is the memory (it may be empty): {memory}"""
74
+
75
+ # Create new memory from the chat history and any existing memory
76
+ CREATE_MEMORY_INSTRUCTION = """Create or update a user profile memory based on the user's chat history.
77
+ This will be saved for long-term memory. If there is an existing memory, simply update it.
78
+ Here is the existing memory (it may be empty): {memory}"""
79
+
80
+ def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):
81
+
82
+ """Load memory from the store and use it to personalize the chatbot's response."""
83
+
84
+ # Get the user ID from the config
85
+ user_id = config["configurable"]["user_id"]
86
+
87
+ # Retrieve memory from the store
88
+ namespace = ("memory", user_id)
89
+ existing_memory = store.get(namespace, "user_memory")
90
+
91
+ # Format the memories for the system prompt
92
+ if existing_memory and existing_memory.value:
93
+ memory_dict = existing_memory.value
94
+ formatted_memory = (
95
+ f"Name: {memory_dict.get('user_name', 'Unknown')}\n"
96
+ f"Interests: {', '.join(memory_dict.get('interests', []))}"
97
+ )
98
+ else:
99
+ formatted_memory = None
100
+
101
+ # Format the memory in the system prompt
102
+ system_msg = MODEL_SYSTEM_MESSAGE.format(memory=formatted_memory)
103
+
104
+ # Respond using memory as well as the chat history
105
+ response = model.invoke([SystemMessage(content=system_msg)]+state["messages"])
106
+
107
+ return {"messages": response}
108
+
109
+ def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
110
+
111
+ """Reflect on the chat history and save a memory to the store."""
112
+
113
+ # Get the user ID from the config
114
+ user_id = config["configurable"]["user_id"]
115
+
116
+ # Retrieve existing memory from the store
117
+ namespace = ("memory", user_id)
118
+ existing_memory = store.get(namespace, "user_memory")
119
+
120
+ # Format the memories for the system prompt
121
+ if existing_memory and existing_memory.value:
122
+ memory_dict = existing_memory.value
123
+ formatted_memory = (
124
+ f"Name: {memory_dict.get('user_name', 'Unknown')}\n"
125
+ f"Interests: {', '.join(memory_dict.get('interests', []))}"
126
+ )
127
+ else:
128
+ formatted_memory = None
129
+
130
+ # Format the existing memory in the instruction
131
+ system_msg = CREATE_MEMORY_INSTRUCTION.format(memory=formatted_memory)
132
+
133
+ # Invoke the model to produce structured output that matches the schema
134
+ new_memory = model_with_structure.invoke([SystemMessage(content=system_msg)]+state['messages'])
135
+
136
+ # Overwrite the existing use profile memory
137
+ key = "user_memory"
138
+ store.put(namespace, key, new_memory)
139
+
140
+ # Define the graph
141
+ builder = StateGraph(MessagesState)
142
+ builder.add_node("call_model", call_model)
143
+ builder.add_node("write_memory", write_memory)
144
+ builder.add_edge(START, "call_model")
145
+ builder.add_edge("call_model", "write_memory")
146
+ builder.add_edge("write_memory", END)
147
+
148
+ # Store for long-term (across-thread) memory
149
+ across_thread_memory = InMemoryStore()
150
+
151
+ # Checkpointer for short-term (within-thread) memory
152
+ within_thread_memory = MemorySaver()
153
+
154
+ # Compile the graph with the checkpointer fir and store
155
+ graph = builder.compile(checkpointer=within_thread_memory, store=across_thread_memory)
156
+
157
+ with open("memoryschema01.png", "wb") as f:
158
+ f.write(graph.get_graph().draw_mermaid_png())
159
+
160
+ # We supply a thread ID for short-term (within-thread) memory
161
+ # We supply a user ID for long-term (across-thread) memory
162
+ config = {"configurable": {"thread_id": "1", "user_id": "1"}}
163
+
164
+ # User input
165
+ input_messages = [HumanMessage(content="Hi, my name is Lance and I like to bike around San Francisco and eat at bakeries.")]
166
+
167
+ print("------------------")
168
+ print("Mensaje 1:")
169
+ print("------------------")
170
+ # Run the graph
171
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
172
+ chunk["messages"][-1].pretty_print()
173
+
174
+
175
+ # Namespace for the memory to save
176
+ user_id = "1"
177
+ namespace = ("memory", user_id)
178
+ existing_memory = across_thread_memory.get(namespace, "user_memory")
179
+ print("------------------")
180
+ print("Memory after first message:")
181
+ print("------------------")
182
+ print(existing_memory.value)
183
+
184
+
185
+
186
+ # Schema TrustCall para crear y actualizar esquemas de perfil
187
+ class UserProfile(BaseModel):
188
+ """ Profile of a user """
189
+ user_name: str = Field(description="The user's preferred name")
190
+ user_location: str = Field(description="The user's location")
191
+ interests: list = Field(description="A list of the user's interests")
192
+
193
+ # Create the extractor
194
+ trustcall_extractor = create_extractor(
195
+ model,
196
+ tools=[UserProfile],
197
+ tool_choice="UserProfile", # Enforces use of the UserProfile tool
198
+ )
199
+
200
+ # Chatbot instruction
201
+ MODEL_SYSTEM_MESSAGE = """You are a helpful assistant with memory that provides information about the user.
202
+ If you have memory for this user, use it to personalize your responses.
203
+ Here is the memory (it may be empty): {memory}"""
204
+
205
+ # Extraction instruction
206
+ TRUSTCALL_INSTRUCTION = """Create or update the memory (JSON doc) to incorporate information from the following conversation:"""
207
+
208
+ def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):
209
+
210
+ """Load memory from the store and use it to personalize the chatbot's response."""
211
+
212
+ # Get the user ID from the config
213
+ user_id = config["configurable"]["user_id"]
214
+
215
+ # Retrieve memory from the store
216
+ namespace = ("memory", user_id)
217
+ existing_memory = store.get(namespace, "user_memory")
218
+
219
+ # Format the memories for the system prompt
220
+ if existing_memory and existing_memory.value:
221
+ memory_dict = existing_memory.value
222
+ formatted_memory = (
223
+ f"Name: {memory_dict.get('user_name', 'Unknown')}\n"
224
+ f"Location: {memory_dict.get('user_location', 'Unknown')}\n"
225
+ f"Interests: {', '.join(memory_dict.get('interests', []))}"
226
+ )
227
+ else:
228
+ formatted_memory = None
229
+
230
+ # Format the memory in the system prompt
231
+ system_msg = MODEL_SYSTEM_MESSAGE.format(memory=formatted_memory)
232
+
233
+ # Respond using memory as well as the chat history
234
+ response = model.invoke([SystemMessage(content=system_msg)]+state["messages"])
235
+
236
+ return {"messages": response}
237
+
238
+ def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
239
+
240
+ """Reflect on the chat history and save a memory to the store."""
241
+
242
+ # Get the user ID from the config
243
+ user_id = config["configurable"]["user_id"]
244
+
245
+ # Retrieve existing memory from the store
246
+ namespace = ("memory", user_id)
247
+ existing_memory = store.get(namespace, "user_memory")
248
+
249
+ # Get the profile as the value from the list, and convert it to a JSON doc
250
+ existing_profile = {"UserProfile": existing_memory.value} if existing_memory else None
251
+
252
+ # Invoke the extractor
253
+ result = trustcall_extractor.invoke({"messages": [SystemMessage(content=TRUSTCALL_INSTRUCTION)]+state["messages"], "existing": existing_profile})
254
+
255
+ # Get the updated profile as a JSON object
256
+ updated_profile = result["responses"][0].model_dump()
257
+
258
+ # Save the updated profile
259
+ key = "user_memory"
260
+ store.put(namespace, key, updated_profile)
261
+
262
+ # Define the graph
263
+ builder = StateGraph(MessagesState)
264
+ builder.add_node("call_model", call_model)
265
+ builder.add_node("write_memory", write_memory)
266
+ builder.add_edge(START, "call_model")
267
+ builder.add_edge("call_model", "write_memory")
268
+ builder.add_edge("write_memory", END)
269
+
270
+ # Store for long-term (across-thread) memory
271
+ across_thread_memory = InMemoryStore()
272
+
273
+ # Checkpointer for short-term (within-thread) memory
274
+ within_thread_memory = MemorySaver()
275
+
276
+ # Compile the graph with the checkpointer fir and store
277
+ graph = builder.compile(checkpointer=within_thread_memory, store=across_thread_memory)
278
+
279
+ # We supply a thread ID for short-term (within-thread) memory
280
+ # We supply a user ID for long-term (across-thread) memory
281
+ config = {"configurable": {"thread_id": "1", "user_id": "1"}}
282
+
283
+ # User input
284
+ input_messages = [HumanMessage(content="Hi, my name is Lance")]
285
+
286
+ print("------------------")
287
+ print("Chatbot with TrustCall: Mensaje 1")
288
+ print("------------------")
289
+ # Run the graph
290
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
291
+ chunk["messages"][-1].pretty_print()
292
+
293
+ # User input
294
+ input_messages = [HumanMessage(content="I like to bike around San Francisco")]
295
+
296
+ print("------------------")
297
+ print("Chatbot with TrustCall: Mensaje 2")
298
+ print("------------------")
299
+ # Run the graph
300
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
301
+ chunk["messages"][-1].pretty_print()
302
+
303
+ print("------------------")
304
+ print("Chatbot with TrustCall: Memory after messages")
305
+ print("------------------")
306
+ # Namespace for the memory to save
307
+ user_id = "1"
308
+ namespace = ("memory", user_id)
309
+ existing_memory = across_thread_memory.get(namespace, "user_memory")
310
+ print(existing_memory.dict())
311
+
312
+ print("------------------")
313
+ print("Chatbot with TrustCall: Mensaje 3")
314
+ print("------------------")
315
+ # User input
316
+ input_messages = [HumanMessage(content="I also enjoy going to bakeries")]
317
+
318
+ # Run the graph
319
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
320
+ chunk["messages"][-1].pretty_print()
321
+
322
+ print("------------------")
323
+ print("Chatbot with TrustCall: Mensaje 4")
324
+ print("------------------")
325
+ # We supply a thread ID for short-term (within-thread) memory
326
+ # We supply a user ID for long-term (across-thread) memory
327
+ config = {"configurable": {"thread_id": "2", "user_id": "1"}}
328
+
329
+ # User input
330
+ input_messages = [HumanMessage(content="What bakeries do you recommend for me?")]
331
+
332
+ # Run the graph
333
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
334
+ chunk["messages"][-1].pretty_print()
memorystore01.png ADDED
memorystore01.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import uuid
3
+ from langgraph.store.memory import InMemoryStore
4
+ from langchain_openai import ChatOpenAI
5
+ from IPython.display import Image, display
6
+
7
+ from langgraph.checkpoint.memory import MemorySaver
8
+ from langgraph.graph import StateGraph, MessagesState, START, END
9
+ from langgraph.store.base import BaseStore
10
+
11
+ from langchain_core.messages import HumanMessage, SystemMessage
12
+ from langchain_core.runnables.config import RunnableConfig
13
+
14
+
15
+ load_dotenv()
16
+ model = ChatOpenAI(model="gpt-4.1-mini", temperature=0)
17
+
18
+
19
+ in_memory_store = InMemoryStore()
20
+ # Namespace for the memory to save
21
+ user_id = "1"
22
+ namespace_for_memory = (user_id, "memories")
23
+
24
+ # Save a memory to namespace as key and value
25
+ key = str(uuid.uuid4())
26
+
27
+ # The value needs to be a dictionary
28
+ value = {"food_preference" : "I like pizza"}
29
+
30
+ # Save the memory
31
+ in_memory_store.put(namespace_for_memory, key, value)
32
+
33
+ # Search
34
+ memories = in_memory_store.search(namespace_for_memory)
35
+ # The key, value
36
+ print("--------------")
37
+ print("Memories:")
38
+ print(memories[0].key, memories[0].value)
39
+ # Get the memory by namespace and key
40
+ memory = in_memory_store.get(namespace_for_memory, key)
41
+ print("--------------")
42
+ print("Memory by key:")
43
+ print(memory.dict())
44
+
45
+ # Chatbot with long-term memory
46
+
47
+ # Chatbot instruction
48
+ MODEL_SYSTEM_MESSAGE = """You are a helpful assistant with memory that provides information about the user.
49
+ If you have memory for this user, use it to personalize your responses.
50
+ Here is the memory (it may be empty): {memory}"""
51
+
52
+ # Create new memory from the chat history and any existing memory
53
+ CREATE_MEMORY_INSTRUCTION = """"You are collecting information about the user to personalize your responses.
54
+
55
+ CURRENT USER INFORMATION:
56
+ {memory}
57
+
58
+ INSTRUCTIONS:
59
+ 1. Review the chat history below carefully
60
+ 2. Identify new information about the user, such as:
61
+ - Personal details (name, location)
62
+ - Preferences (likes, dislikes)
63
+ - Interests and hobbies
64
+ - Past experiences
65
+ - Goals or future plans
66
+ 3. Merge any new information with existing memory
67
+ 4. Format the memory as a clear, bulleted list
68
+ 5. If new information conflicts with existing memory, keep the most recent version
69
+
70
+ Remember: Only include factual information directly stated by the user. Do not make assumptions or inferences.
71
+
72
+ Based on the chat history below, please update the user information:"""
73
+
74
+ def call_model(state: MessagesState, config: RunnableConfig, store: BaseStore):
75
+
76
+ """Load memory from the store and use it to personalize the chatbot's response."""
77
+
78
+ # Get the user ID from the config
79
+ user_id = config["configurable"]["user_id"]
80
+
81
+ # Retrieve memory from the store
82
+ namespace = ("memory", user_id)
83
+ key = "user_memory"
84
+ existing_memory = store.get(namespace, key)
85
+
86
+ # Extract the actual memory content if it exists and add a prefix
87
+ if existing_memory:
88
+ # Value is a dictionary with a memory key
89
+ existing_memory_content = existing_memory.value.get('memory')
90
+ else:
91
+ existing_memory_content = "No existing memory found."
92
+
93
+ # Format the memory in the system prompt
94
+ system_msg = MODEL_SYSTEM_MESSAGE.format(memory=existing_memory_content)
95
+
96
+ # Respond using memory as well as the chat history
97
+ response = model.invoke([SystemMessage(content=system_msg)]+state["messages"])
98
+
99
+ return {"messages": response}
100
+
101
+ def write_memory(state: MessagesState, config: RunnableConfig, store: BaseStore):
102
+
103
+ """Reflect on the chat history and save a memory to the store."""
104
+
105
+ # Get the user ID from the config
106
+ user_id = config["configurable"]["user_id"]
107
+
108
+ # Retrieve existing memory from the store
109
+ namespace = ("memory", user_id)
110
+ existing_memory = store.get(namespace, "user_memory")
111
+
112
+ # Extract the memory
113
+ if existing_memory:
114
+ existing_memory_content = existing_memory.value.get('memory')
115
+ else:
116
+ existing_memory_content = "No existing memory found."
117
+
118
+ # Format the memory in the system prompt
119
+ system_msg = CREATE_MEMORY_INSTRUCTION.format(memory=existing_memory_content)
120
+ new_memory = model.invoke([SystemMessage(content=system_msg)]+state['messages'])
121
+
122
+ # Overwrite the existing memory in the store
123
+ key = "user_memory"
124
+
125
+ # Write value as a dictionary with a memory key
126
+ store.put(namespace, key, {"memory": new_memory.content})
127
+
128
+ # Define the graph
129
+ builder = StateGraph(MessagesState)
130
+ builder.add_node("call_model", call_model)
131
+ builder.add_node("write_memory", write_memory)
132
+ builder.add_edge(START, "call_model")
133
+ builder.add_edge("call_model", "write_memory")
134
+ builder.add_edge("write_memory", END)
135
+
136
+ # Store for long-term (across-thread) memory
137
+ across_thread_memory = InMemoryStore()
138
+
139
+ # Checkpointer for short-term (within-thread) memory
140
+ within_thread_memory = MemorySaver()
141
+
142
+ # Compile the graph with the checkpointer fir and store
143
+ graph = builder.compile(checkpointer=within_thread_memory, store=across_thread_memory)
144
+
145
+ # with open("memorystore01.png", "wb") as f:
146
+ # f.write(graph.get_graph().draw_mermaid_png())
147
+
148
+ # We supply a thread ID for short-term (within-thread) memory
149
+ # We supply a user ID for long-term (across-thread) memory
150
+ config = {"configurable": {"thread_id": "1", "user_id": "1"}}
151
+
152
+
153
+ print("-------------------")
154
+ print("Mensaje 1")
155
+ print("-------------------")
156
+ # User input
157
+ input_messages = [HumanMessage(content="Hi, my name is Lance")]
158
+
159
+ # Run the graph
160
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
161
+ chunk["messages"][-1].pretty_print()
162
+
163
+ print("-------------------")
164
+ print("Mensaje 2")
165
+ print("-------------------")
166
+ # User input
167
+ input_messages = [HumanMessage(content="I like to bike around San Francisco")]
168
+
169
+ # Run the graph
170
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
171
+ chunk["messages"][-1].pretty_print()
172
+
173
+ print("-------------------")
174
+ print("Mensaje Hilo")
175
+ print("-------------------")
176
+ thread = {"configurable": {"thread_id": "1"}}
177
+ state = graph.get_state(thread).values
178
+ for m in state["messages"]:
179
+ m.pretty_print()
180
+
181
+ print("-------------------")
182
+ print("user memory")
183
+ print("-------------------")
184
+ # Namespace for the memory to save
185
+ user_id = "1"
186
+ namespace = ("memory", user_id)
187
+ existing_memory = across_thread_memory.get(namespace, "user_memory")
188
+ print(existing_memory.dict())
189
+
190
+ print("-------------------")
191
+ print("thread_id 2")
192
+ print("-------------------")
193
+ # We supply a user ID for across-thread memory as well as a new thread ID
194
+ config = {"configurable": {"thread_id": "2", "user_id": "1"}}
195
+
196
+ # User input
197
+ input_messages = [HumanMessage(content="Hi! Where would you recommend that I go biking?")]
198
+
199
+ # Run the graph
200
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
201
+ chunk["messages"][-1].pretty_print()
202
+
203
+ print("-------------------")
204
+ print("Last")
205
+ print("-------------------")
206
+ # User input
207
+ input_messages = [HumanMessage(content="Great, are there any bakeries nearby that I can check out? I like a croissant after biking.")]
208
+
209
+ # Run the graph
210
+ for chunk in graph.stream({"messages": input_messages}, config, stream_mode="values"):
211
+ chunk["messages"][-1].pretty_print()