diff --git a/libs/aws/langchain_aws/memory/__init__.py b/libs/aws/langchain_aws/memory/__init__.py new file mode 100644 index 00000000..c05c6835 --- /dev/null +++ b/libs/aws/langchain_aws/memory/__init__.py @@ -0,0 +1,13 @@ +"""Memory module for AWS Bedrock Agent Core.""" + +from langchain_aws.memory.bedrock_agentcore import ( + create_list_messages_tool, + create_search_memory_tool, + create_store_messages_tool, +) + +__all__ = [ + "create_store_messages_tool", + "create_search_memory_tool", + "create_list_messages_tool" +] \ No newline at end of file diff --git a/libs/aws/langchain_aws/memory/bedrock_agentcore.py b/libs/aws/langchain_aws/memory/bedrock_agentcore.py new file mode 100644 index 00000000..7ad250cf --- /dev/null +++ b/libs/aws/langchain_aws/memory/bedrock_agentcore.py @@ -0,0 +1,337 @@ +"""Module for AWS Bedrock Agent Core memory integration. + +This module provides integration between LangChain/LangGraph and AWS Bedrock Agent Core +memory API. It includes a memory store implementation and tools for managing and +searching memories. +""" + +import json +import logging +from typing import List + +from bedrock_agentcore.memory import MemoryClient +from langchain_core.messages import AIMessage, BaseMessage, HumanMessage +from langchain_core.runnables import RunnableConfig +from langchain_core.tools import StructuredTool + +logger = logging.getLogger(__name__) + + +def create_store_messages_tool( + memory_client: MemoryClient, + name: str = "store_messages" +) -> StructuredTool: + """Create a tool for storing messages directly with Bedrock Agent Core MemoryClient. + + This tool enables AI assistants to store messages in Bedrock Agent Core. + The tool expects the following configuration values to be passed via RunnableConfig: + - memory_id: The ID of the memory to store in + - actor_id: (optional) The actor ID to use + - session_id: (optional) The session ID to use + + Args: + memory_client: The MemoryClient instance to use + name: The name of the tool + + Returns: + A structured tool for storing messages + """ + + instructions = ( + "Use this tool to store all messages from the user and AI model. These " + "messages are processed to extract summary or facts of the conversation, " + "which can be later retrieved using the search_memory tool." + ) + + def store_messages( + messages: List[BaseMessage], + config: RunnableConfig, + ) -> str: + """Stores conversation messages in AWS Bedrock Agent Core Memory. + + Args: + messages: List of messages to store + + Returns: + A confirmation message. + """ + if not (configurable := config.get("configurable", None)): + raise ValueError( + "A runtime config containing memory_id, actor_id, and session_id is required." + ) + + if not (memory_id := configurable.get("memory_id", None)): + raise ValueError( + "Missing memory_id in the runtime config." + ) + + if not (session_id := configurable.get("session_id", None)): + raise ValueError( + "Missing session_id in the runtime config." + ) + + if not (actor_id := configurable.get("actor_id", None)): + raise ValueError( + "Missing actor_id in the runtime config." + ) + + # Convert BaseMessage list to list of (text, role) tuples + # TODO: This should correctly convert to + converted_messages = [] + for msg in messages: + + # Skip if event already saved + if msg.additional_kwargs.get("event_id", None) is not None: + continue + + # Extract text content + content = msg.content + if isinstance(content, str): + text = content + elif isinstance(content, dict) and content['type'] == 'text': + text = content['text'] + else: + continue + + # Map LangChain roles to Bedrock Agent Core roles + # Available roles in Bedrock: USER, ASSISTANT, TOOL + if msg.type == "human": + role = "USER" + elif msg.type == "ai": + role = "ASSISTANT" + elif msg.type == "tool": + role = "TOOL" + else: + continue # Skip unsupported message types + + converted_messages.append((text, role)) + + # Create event with converted messages directly using the MemoryClient + response = memory_client.create_event( + memory_id=memory_id, + actor_id=actor_id, + session_id=session_id, + messages=converted_messages + ) + + return f"Memory created with ID: {response.get('eventId')}" + + # Create a StructuredTool with the custom name + return StructuredTool.from_function( + func=store_messages, name=name, description=instructions + ) + + +def create_list_messages_tool( + memory_client: MemoryClient, + name: str = "list_messages", +) -> StructuredTool: + """Create a tool for listing conversation messages from Bedrock Agent Core Memory. + + This tool allows AI assistants to retrieve the message history from a conversation + stored in Bedrock Agent Core Memory. + + The tool expects the following configuration values to be passed via RunnableConfig: + - memory_id: The ID of the memory to retrieve from (required) + - actor_id: The actor ID to use (required) + - session_id: The session ID to use (required) + + Args: + memory_client: The MemoryClient instance to use + name: The name of the tool + + Returns: + A structured tool for listing conversation messages + """ + + instructions = ( + "Use this tool to retrieve the conversation history from memory. " + "This can help in understanding the context of the current conversation, " + "or reviewing past interactions." + ) + + def list_messages( + max_results: int = 100, + config: RunnableConfig = None, + ) -> List[BaseMessage]: + """List conversation messages from AWS Bedrock Agent Core Memory. + + Args: + max_results: Maximum number of messages to return + config: RunnableConfig containing memory_id, actor_id, and session_id + + Returns: + A list of LangChain message objects (HumanMessage, AIMessage, ToolMessage) + """ + if not (configurable := config.get("configurable", None)): + raise ValueError( + "A runtime config with memory_id, actor_id, and session_id is required" + " for list_messages tool." + ) + + if not (memory_id := configurable.get("memory_id", None)): + raise ValueError( + "Missing memory_id in the runtime config." + ) + + if not (actor_id := configurable.get("actor_id", None)): + raise ValueError( + "Missing actor_id in the runtime config." + ) + + if not (session_id := configurable.get("session_id", None)): + raise ValueError( + "Missing session_id in the runtime config." + ) + + events = memory_client.list_events( + memory_id=memory_id, + actor_id=actor_id, + session_id=session_id, + max_results=max_results, + include_payload=True + ) + + # Extract and format messages as LangChain message objects + messages = [] + for event in events: + # Extract messages from event payload + if "payload" in event: + for payload_item in event.get("payload", []): + if "conversational" in payload_item: + conv = payload_item["conversational"] + role = conv.get("role", "") + content = conv.get("content", {}).get("text", "") + + # Convert to appropriate LangChain message type based on role + if role == "USER": + message = HumanMessage(content=content) + elif role == "ASSISTANT": + message = AIMessage(content=content) + elif role == "TOOL": + #message = ToolMessage(content=content, tool_call_id="unknown") + # skipping tool events as tool_call_id etc. will be missing + continue + else: + # Skip unknown message types + continue + + # Add metadata if available + if "eventId" in event: + message.additional_kwargs["event_id"] = event["eventId"] + if "eventTimestamp" in event: + pass + # Skip this, this currently not serialized correctly + # message.additional_kwargs["timestamp"] = event["eventTimestamp"] + + messages.append(message) + + return messages + + # Create a StructuredTool with the custom name + return StructuredTool.from_function( + func=list_messages, name=name, description=instructions + ) + + +def create_search_memory_tool( + memory_client: MemoryClient, + name: str = "search_memory", +) -> StructuredTool: + """Create a tool for searching memories in AWS Bedrock Agent Core. + + This tool allows AI assistants to search through stored memories in AWS + Bedrock Agent Core using semantic search. + + The tool expects the following configuration values to be passed via RunnableConfig: + - memory_id: The ID of the memory to search in (required) + - namespace: The namespace to search in (required) + + Args: + memory_client: The MemoryClient instance to use + name: The name of the tool + + Returns: + A structured tool for searching memories. + """ + + instructions = ( + "Use this tool to search for helpful facts and preferences from the past " + "conversations. Based on the namespace and configured memories, this will " + "provide summaries, user preferences or semantic search for the session." + ) + + def search_memory( + query: str, + limit: int = 3, + config: RunnableConfig = None, + ) -> str: + """Search for memories in AWS Bedrock Agent Core. + + Args: + query: The search query to find relevant memories. + limit: Maximum number of results to return. + + Returns: + A string representation of the search results. + """ + if not (configurable := config.get("configurable", None)): + raise ValueError( + "A runtime config with memory_id, namespace, and actor_id is required." + ) + + if not (memory_id := configurable.get("memory_id", None)): + raise ValueError( + "Missing memory_id in the runtime config." + ) + + # Namespace is required + if not (namespace_val := configurable.get("namespace", None)): + raise ValueError( + "Missing namespace in the runtime config." + ) + + # Format the namespace + if isinstance(namespace_val, tuple): + # Join tuple elements with '/' + namespace_str = "/" + "/".join(namespace_val) + elif isinstance(namespace_val, str): + # Ensure string starts with '/' + namespace_str = namespace_val if namespace_val.startswith("/") else f"/{namespace_val}" + else: + raise ValueError( + f"Namespace must be a string or tuple, got {type(namespace_val)}" + ) + + # Perform the search directly using the MemoryClient + memories = memory_client.retrieve_memories( + memory_id=memory_id, + namespace=namespace_str, + query=query, + top_k=limit, + ) + + # Process and format results + results = [] + for item in memories: + # Extract content from the memory item + content = item.get("content", {}).get("text", "") + + # Try to parse JSON content if it looks like JSON + if content and content.startswith("{") and content.endswith("}"): + try: + content = json.loads(content) + except json.JSONDecodeError: + pass + + results.append(content) + + return results + + + # Create a StructuredTool with the custom name + return StructuredTool.from_function( + func=search_memory, + name=name, + description=instructions + ) diff --git a/samples/memory/bedrock-memory-with-hooks.ipynb b/samples/memory/bedrock-memory-with-hooks.ipynb new file mode 100644 index 00000000..0b90687d --- /dev/null +++ b/samples/memory/bedrock-memory-with-hooks.ipynb @@ -0,0 +1,597 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "9eb8d74d-b7c3-48b5-ad2b-05b2c5804221", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.prebuilt import create_react_agent\n", + "\n", + "from langchain_aws.memory import (\n", + " create_store_messages_tool,\n", + " create_search_memory_tool,\n", + " create_list_messages_tool\n", + ")\n", + "from bedrock_agentcore.memory import MemoryClient" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "884a3186-eaca-4b5c-ae15-d74a1fc3b15f", + "metadata": {}, + "outputs": [], + "source": [ + "REGION = \"us-east-1\"" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5de1cd58-626e-4aec-be5c-fefde2f5f36d", + "metadata": {}, + "outputs": [], + "source": [ + "memory_client = MemoryClient(region_name=REGION)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfcc8118-046e-4c2a-b6b5-b41e91128eb5", + "metadata": {}, + "outputs": [], + "source": [ + "create_response = memory_client.create_memory_and_wait(\n", + " name=\"CustomerSupportAgentMemory\",\n", + " strategies=[{\n", + " \"summaryMemoryStrategy\": {\n", + " \"name\": \"SessionSummarizer\",\n", + " \"namespaces\": [\"/summaries/{actorId}/{sessionId}\"]\n", + " }\n", + " }]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "40c159db-0e86-4a42-925e-809b07d1cb46", + "metadata": {}, + "outputs": [], + "source": [ + "MEMORY_ID = create_response[\"id\"]\n", + "ACTOR_ID = \"User4\"\n", + "SESSION_ID = \"OrderSupportSession4\"" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "3d5eab54-af4c-47dc-b7c1-7897ef085f21", + "metadata": {}, + "outputs": [], + "source": [ + "# Create memory management tool\n", + "store_messages_tool = create_store_messages_tool(\n", + " memory_client=memory_client\n", + ")\n", + "\n", + "# Create memory search tool\n", + "search_memory_tool = create_search_memory_tool(\n", + " memory_client=memory_client\n", + ")\n", + "\n", + "# Create the list messages tool\n", + "list_messages_tool = create_list_messages_tool(\n", + " memory_client=memory_client\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f37264b8-050e-40e5-91a2-2cbc06155793", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "@tool\n", + "def lookup_order(order_id: str) -> str:\n", + " \"\"\"Returns the order status\"\"\"\n", + " return f\"Customer order {order_id} was shipped 3 days ago, with an expected delivery tomorrow before 5pm\"\n", + "\n", + "@tool\n", + "def update_customer_email(email: str):\n", + " \"\"\"Updates customer's email address\"\"\"\n", + " return f\"Customer's email updated to {email}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "47cc0beb-0a3b-4e43-998d-2dae590e4f0f", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.checkpoint.memory import InMemorySaver\n", + "from langchain_core.messages import HumanMessage, SystemMessage\n", + "\n", + "llm_model = \"bedrock_converse:us.anthropic.claude-3-5-haiku-20241022-v1:0\"\n", + "prompt_template = \"You are a customer service assistant. Here is the conversation context: {summary}\"\n", + "\n", + "def pre_model_hook(state):\n", + " messages = state[\"messages\"]\n", + "\n", + " # Get summary or other long term memories\n", + " last_message = messages[-1]\n", + " summary = \"\"\n", + " if isinstance(last_message, HumanMessage):\n", + " memories = search_memory_tool.invoke({\n", + " \"query\": last_message.content\n", + " })\n", + " if len(memories) > 0:\n", + " summary = memories[0]\n", + "\n", + " # Get previous conversations, trimming to last 2\n", + " previous_messages = list_messages_tool.invoke({\"max_results\": 2}, config=config)\n", + " previous_messages.reverse()\n", + "\n", + " # System message, that will have the long\n", + " # term memory context\n", + " system_message = SystemMessage(\n", + " content=prompt_template.format(summary=summary)\n", + " )\n", + "\n", + " messages = [system_message] + previous_messages + messages\n", + "\n", + " return {\n", + " \"llm_input_messages\": messages\n", + " }\n", + "\n", + "def post_model_hook(state):\n", + " store_messages_tool.invoke({\n", + " \"messages\": state[\"messages\"]\n", + " })\n", + "\n", + "agent = create_react_agent(\n", + " llm_model,\n", + " tools=[lookup_order, update_customer_email],\n", + " pre_model_hook=pre_model_hook,\n", + " post_model_hook=post_model_hook\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "8f587a32-70c3-423b-9787-a6295dca976b", + "metadata": {}, + "outputs": [], + "source": [ + "config = {\n", + " \"configurable\": {\n", + " \"memory_id\": MEMORY_ID,\n", + " \"session_id\": SESSION_ID,\n", + " \"actor_id\": ACTOR_ID,\n", + " \"namespace\": f\"/summaries/{ACTOR_ID}/{SESSION_ID}\"\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "fbeed506-596c-44d4-8f6a-701f6229263a", + "metadata": {}, + "outputs": [], + "source": [ + "def list_events():\n", + " events = memory_client.list_events(memory_id=MEMORY_ID, session_id=SESSION_ID, actor_id=ACTOR_ID)\n", + " for event in events:\n", + " print(f\"\\n================================ Event Id: {event['eventId']} =================================\\n\")\n", + " for payload in event['payload']:\n", + " print(f\"================================ {payload['conversational']['role']} =================================\")\n", + " print(payload['conversational']['content']['text'])" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "4beeb2c2-c884-4025-88b9-ca433de0aba8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Hi, I'm having trouble with my order #12345\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'type': 'text', 'text': \"Let me help you check the status of your order. I'll look up the details for order #12345 right away.\"}, {'type': 'tool_use', 'name': 'lookup_order', 'input': {'order_id': '12345'}, 'id': 'tooluse_beyZ90h7TPGdwIMjqYK6Xw'}]\n", + "Tool Calls:\n", + " lookup_order (tooluse_beyZ90h7TPGdwIMjqYK6Xw)\n", + " Call ID: tooluse_beyZ90h7TPGdwIMjqYK6Xw\n", + " Args:\n", + " order_id: 12345\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: lookup_order\n", + "\n", + "Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "I see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that's causing you concern? I'd be happy to help you further or provide more information about the shipment.\n" + ] + } + ], + "source": [ + "response = agent.invoke({\n", + " \"messages\": [\n", + " \"Hi, I'm having trouble with my order #12345\"\n", + " ]\n", + "}, config)\n", + "\n", + "for message in response[\"messages\"]:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ead49358-8680-4384-a3f7-0789d6105123", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================ Event Id: 0000001753926824000#46374f3d =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n", + "================================ TOOL =================================\n", + "Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm\n", + "================================ ASSISTANT =================================\n", + "I see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that's causing you concern? I'd be happy to help you further or provide more information about the shipment.\n", + "\n", + "================================ Event Id: 0000001753926822000#47403cb4 =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n" + ] + } + ], + "source": [ + "list_events()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "b0352a95-f134-4020-8523-73296bc4dcc8", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Actually, before that - I also want to change my email address\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "I'll help you with updating your email address first, and then we'll check the status of your order.\n", + "\n", + "Could you please provide me with the new email address you'd like to use?\n" + ] + } + ], + "source": [ + "response = agent.invoke({\n", + " \"messages\": [\n", + " \"Actually, before that - I also want to change my email address\"\n", + " ]\n", + "}, config)\n", + "for message in response[\"messages\"]:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "0ac0c169-f47d-49c6-b711-83b28a7e74e0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================ Event Id: 0000001753926839000#d5052028 =================================\n", + "\n", + "================================ USER =================================\n", + "Actually, before that - I also want to change my email address\n", + "================================ ASSISTANT =================================\n", + "I'll help you with updating your email address first, and then we'll check the status of your order.\n", + "\n", + "Could you please provide me with the new email address you'd like to use?\n", + "\n", + "================================ Event Id: 0000001753926824000#46374f3d =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n", + "================================ TOOL =================================\n", + "Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm\n", + "================================ ASSISTANT =================================\n", + "I see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that's causing you concern? I'd be happy to help you further or provide more information about the shipment.\n", + "\n", + "================================ Event Id: 0000001753926822000#47403cb4 =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n" + ] + } + ], + "source": [ + "list_events()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "3492a73c-74d8-4abe-b787-b7944b8ca235", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "myemail@example.com\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'type': 'text', 'text': \"I'll help you update your email address right away.\"}, {'type': 'tool_use', 'name': 'update_customer_email', 'input': {'email': 'myemail@example.com'}, 'id': 'tooluse_UEBGmgAQTR28Mobm7THbgw'}]\n", + "Tool Calls:\n", + " update_customer_email (tooluse_UEBGmgAQTR28Mobm7THbgw)\n", + " Call ID: tooluse_UEBGmgAQTR28Mobm7THbgw\n", + " Args:\n", + " email: myemail@example.com\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: update_customer_email\n", + "\n", + "Customer's email updated to myemail@example.com\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Your email address has been successfully updated to myemail@example.com. \n", + "\n", + "Is there anything else I can help you with today? Would you like to check the status of an order?\n" + ] + } + ], + "source": [ + "response = agent.invoke({\n", + " \"messages\": [\n", + " \"myemail@example.com\"\n", + " ]\n", + "}, config)\n", + "for message in response[\"messages\"]:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "5aefe00e-3978-4a6a-b88c-6663e59d1ee2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================ Event Id: 0000001753926858000#eed1ac65 =================================\n", + "\n", + "================================ USER =================================\n", + "myemail@example.com\n", + "================================ TOOL =================================\n", + "Customer's email updated to myemail@example.com\n", + "================================ ASSISTANT =================================\n", + "Your email address has been successfully updated to myemail@example.com. \n", + "\n", + "Is there anything else I can help you with today? Would you like to check the status of an order?\n", + "\n", + "================================ Event Id: 0000001753926857000#e22d00a2 =================================\n", + "\n", + "================================ USER =================================\n", + "myemail@example.com\n", + "\n", + "================================ Event Id: 0000001753926839000#d5052028 =================================\n", + "\n", + "================================ USER =================================\n", + "Actually, before that - I also want to change my email address\n", + "================================ ASSISTANT =================================\n", + "I'll help you with updating your email address first, and then we'll check the status of your order.\n", + "\n", + "Could you please provide me with the new email address you'd like to use?\n", + "\n", + "================================ Event Id: 0000001753926824000#46374f3d =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n", + "================================ TOOL =================================\n", + "Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm\n", + "================================ ASSISTANT =================================\n", + "I see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that's causing you concern? I'd be happy to help you further or provide more information about the shipment.\n", + "\n", + "================================ Event Id: 0000001753926822000#47403cb4 =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n" + ] + } + ], + "source": [ + "list_events()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "17756a30-5e31-4ef6-9453-0b5c3135058d", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "The package already arrived, but seems damaged\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'type': 'text', 'text': 'I apologize to hear that the package arrived damaged. Let me help you verify the order details first.'}, {'type': 'tool_use', 'name': 'lookup_order', 'input': {'order_id': '12345'}, 'id': 'tooluse_ryehEI-SRd2ckquOtKs0MA'}]\n", + "Tool Calls:\n", + " lookup_order (tooluse_ryehEI-SRd2ckquOtKs0MA)\n", + " Call ID: tooluse_ryehEI-SRd2ckquOtKs0MA\n", + " Args:\n", + " order_id: 12345\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: lookup_order\n", + "\n", + "Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "I see that the order was shipped recently. However, since you mentioned the package has already arrived and is damaged, I recommend the following steps:\n", + "\n", + "1. Take clear photos of the damaged package and the damaged items.\n", + "2. Contact our customer support team with the order details and photos.\n", + "3. We can help you with a replacement or refund.\n", + "\n", + "Would you like me to help you further with this issue? Could you provide me with the specific order number so I can assist you more accurately?\n" + ] + } + ], + "source": [ + "response = agent.invoke({\n", + " \"messages\": [\n", + " \"The package already arrived, but seems damaged\"\n", + " ]\n", + "}, config)\n", + "for message in response[\"messages\"]:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "aa616877-3563-4bd3-9a2f-1f3b24ee8965", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================ Event Id: 0000001753926912000#095ae727 =================================\n", + "\n", + "================================ USER =================================\n", + "The package already arrived, but seems damaged\n", + "================================ TOOL =================================\n", + "Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm\n", + "================================ ASSISTANT =================================\n", + "I see that the order was shipped recently. However, since you mentioned the package has already arrived and is damaged, I recommend the following steps:\n", + "\n", + "1. Take clear photos of the damaged package and the damaged items.\n", + "2. Contact our customer support team with the order details and photos.\n", + "3. We can help you with a replacement or refund.\n", + "\n", + "Would you like me to help you further with this issue? Could you provide me with the specific order number so I can assist you more accurately?\n", + "\n", + "================================ Event Id: 0000001753926909000#ebf8d68d =================================\n", + "\n", + "================================ USER =================================\n", + "The package already arrived, but seems damaged\n", + "\n", + "================================ Event Id: 0000001753926858000#eed1ac65 =================================\n", + "\n", + "================================ USER =================================\n", + "myemail@example.com\n", + "================================ TOOL =================================\n", + "Customer's email updated to myemail@example.com\n", + "================================ ASSISTANT =================================\n", + "Your email address has been successfully updated to myemail@example.com. \n", + "\n", + "Is there anything else I can help you with today? Would you like to check the status of an order?\n", + "\n", + "================================ Event Id: 0000001753926857000#e22d00a2 =================================\n", + "\n", + "================================ USER =================================\n", + "myemail@example.com\n", + "\n", + "================================ Event Id: 0000001753926839000#d5052028 =================================\n", + "\n", + "================================ USER =================================\n", + "Actually, before that - I also want to change my email address\n", + "================================ ASSISTANT =================================\n", + "I'll help you with updating your email address first, and then we'll check the status of your order.\n", + "\n", + "Could you please provide me with the new email address you'd like to use?\n", + "\n", + "================================ Event Id: 0000001753926824000#46374f3d =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n", + "================================ TOOL =================================\n", + "Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm\n", + "================================ ASSISTANT =================================\n", + "I see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that's causing you concern? I'd be happy to help you further or provide more information about the shipment.\n", + "\n", + "================================ Event Id: 0000001753926822000#47403cb4 =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n" + ] + } + ], + "source": [ + "list_events()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f060f82-c718-4d1b-81d6-29b9163b0bf6", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/samples/memory/bedrock-memory-with-tools.ipynb b/samples/memory/bedrock-memory-with-tools.ipynb new file mode 100644 index 00000000..af1b1228 --- /dev/null +++ b/samples/memory/bedrock-memory-with-tools.ipynb @@ -0,0 +1,448 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "# Install dependencies if needed\n", + "!pip install langgraph bedrock-agentcore" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import uuid\n", + "import json\n", + "from typing import Dict, Any, List, Optional\n", + "\n", + "from langgraph.prebuilt import create_react_agent\n", + "\n", + "from langchain_aws.memory import (\n", + " create_store_messages_tool,\n", + " create_search_memory_tool,\n", + " create_list_messages_tool\n", + ")\n", + "from bedrock_agentcore.memory import MemoryClient" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "REGION = \"us-east-1\" " + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "memory_client = MemoryClient(region_name=REGION)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "create_response = memory_client.create_memory_and_wait(\n", + " name=\"CustomerSupportAgentMemory\",\n", + " strategies=[{\n", + " \"summaryMemoryStrategy\": {\n", + " \"name\": \"SessionSummarizer\",\n", + " \"namespaces\": [\"/summaries/{actorId}/{sessionId}\"]\n", + " }\n", + " }]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "MEMORY_ID = create_response[\"id\"]\n", + "ACTOR_ID = \"User99\"\n", + "SESSION_ID = \"OrderSupportSession5\"" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "def list_events():\n", + " events = memory_client.list_events(memory_id=MEMORY_ID, session_id=SESSION_ID, actor_id=ACTOR_ID)\n", + " for event in events:\n", + " print(f\"\\n================================ Event Id: {event['eventId']} =================================\\n\")\n", + " for payload in event['payload']:\n", + " print(f\"================================ {payload['conversational']['role']} =================================\")\n", + " print(payload['conversational']['content']['text'])" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "list_events()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "store_messages_tool = create_store_messages_tool(\n", + " memory_client=memory_client\n", + ")\n", + "\n", + "search_memory_tool = create_search_memory_tool(\n", + " memory_client=memory_client\n", + ")\n", + "\n", + "list_messages_tool = create_list_messages_tool(\n", + " memory_client=memory_client\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.tools import tool\n", + "\n", + "@tool\n", + "def lookup_order(order_id: str) -> str:\n", + " \"\"\"Returns the order status\"\"\"\n", + " return f\"Customer order {order_id} was shipped 3 days ago, with an expected delivery tomorrow before 5pm\"\n", + "\n", + "@tool\n", + "def update_customer_email(email: str):\n", + " \"\"\"Updates customer's email address\"\"\"\n", + " return f\"Customer's email updated to {email}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "llm_model = \"bedrock_converse:us.anthropic.claude-3-5-haiku-20241022-v1:0\"\n", + "prompt = \"\"\"You are are customer service assistant with access to order and memory tools.\"\"\"\n", + "\n", + "agent = create_react_agent(\n", + " llm_model,\n", + " tools=[store_messages_tool, search_memory_tool, list_messages_tool, lookup_order, update_customer_email],\n", + " prompt=prompt\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "config = {\n", + " \"configurable\": {\n", + " \"memory_id\": MEMORY_ID,\n", + " \"session_id\": SESSION_ID,\n", + " \"actor_id\": ACTOR_ID,\n", + " \"namespace\": f\"/summaries/{ACTOR_ID}/{SESSION_ID}\"\n", + " }\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Hi, I'm having trouble with my order #12345\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'type': 'text', 'text': \"I'll help you look up the status of your order right away.\"}, {'type': 'tool_use', 'name': 'lookup_order', 'input': {'order_id': '12345'}, 'id': 'tooluse_ub-cWkE_Tc-2ItPbhbqT0A'}]\n", + "Tool Calls:\n", + " lookup_order (tooluse_ub-cWkE_Tc-2ItPbhbqT0A)\n", + " Call ID: tooluse_ub-cWkE_Tc-2ItPbhbqT0A\n", + " Args:\n", + " order_id: 12345\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: lookup_order\n", + "\n", + "Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'type': 'text', 'text': \"I can see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that is causing you concern? I'm here to help you with any questions or issues you might be experiencing.\"}, {'type': 'tool_use', 'name': 'store_messages', 'input': {'messages': [{'type': 'human', 'content': \"Hi, I'm having trouble with my order #12345\"}, {'type': 'ai', 'content': \"I'll help you look up the status of your order right away. Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm. I can see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that is causing you concern? I'm here to help you with any questions or issues you might be experiencing.\"}]}, 'id': 'tooluse_R9RTj2qkRm6P-BkzJBWX0g'}]\n", + "Tool Calls:\n", + " store_messages (tooluse_R9RTj2qkRm6P-BkzJBWX0g)\n", + " Call ID: tooluse_R9RTj2qkRm6P-BkzJBWX0g\n", + " Args:\n", + " messages: [{'type': 'human', 'content': \"Hi, I'm having trouble with my order #12345\"}, {'type': 'ai', 'content': \"I'll help you look up the status of your order right away. Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm. I can see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that is causing you concern? I'm here to help you with any questions or issues you might be experiencing.\"}]\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: store_messages\n", + "\n", + "Memory created with ID: 0000001753929112000#5996cf1c\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[]\n" + ] + } + ], + "source": [ + "response = agent.invoke({\n", + " \"messages\": [\n", + " \"Hi, I'm having trouble with my order #12345\"\n", + " ]\n", + "}, config)\n", + "\n", + "for message in response[\"messages\"]:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================ Event Id: 0000001753929112000#5996cf1c =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n", + "================================ ASSISTANT =================================\n", + "I'll help you look up the status of your order right away. Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm. I can see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that is causing you concern? I'm here to help you with any questions or issues you might be experiencing.\n" + ] + } + ], + "source": [ + "list_events()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "Actually, before that - I also want to change my email address\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "I can help you update your email address. Could you please provide me with the new email address you would like to use?\n", + "\n", + "I'll use the `update_customer_email` tool to make this change for you once you share the new email address.\n" + ] + } + ], + "source": [ + "response = agent.invoke({\n", + " \"messages\": [\n", + " \"Actually, before that - I also want to change my email address\"\n", + " ]\n", + "}, config)\n", + "for message in response[\"messages\"]:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================ Event Id: 0000001753929112000#5996cf1c =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n", + "================================ ASSISTANT =================================\n", + "I'll help you look up the status of your order right away. Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm. I can see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that is causing you concern? I'm here to help you with any questions or issues you might be experiencing.\n" + ] + } + ], + "source": [ + "list_events()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "myemail@example.com\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "[{'type': 'text', 'text': \"I'll help you update your email address. I'll use the `update_customer_email` tool to do this.\"}, {'type': 'tool_use', 'name': 'update_customer_email', 'input': {'email': 'myemail@example.com'}, 'id': 'tooluse_m-5o4fUXRrG4zamhpoEW2g'}]\n", + "Tool Calls:\n", + " update_customer_email (tooluse_m-5o4fUXRrG4zamhpoEW2g)\n", + " Call ID: tooluse_m-5o4fUXRrG4zamhpoEW2g\n", + " Args:\n", + " email: myemail@example.com\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: update_customer_email\n", + "\n", + "Customer's email updated to myemail@example.com\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "Your email address has been successfully updated to myemail@example.com. Is there anything else I can help you with today?\n" + ] + } + ], + "source": [ + "response = agent.invoke({\n", + " \"messages\": [\n", + " \"myemail@example.com\"\n", + " ]\n", + "}, config)\n", + "for message in response[\"messages\"]:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================ Event Id: 0000001753929112000#5996cf1c =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n", + "================================ ASSISTANT =================================\n", + "I'll help you look up the status of your order right away. Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm. I can see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that is causing you concern? I'm here to help you with any questions or issues you might be experiencing.\n" + ] + } + ], + "source": [ + "list_events()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "The package already arrived, but seems damaged\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "I'm sorry to hear that your package arrived damaged. I'll help you resolve this issue. Could you please provide me with your order number so I can look up the details of your order and assist you further?\n" + ] + } + ], + "source": [ + "response = agent.invoke({\n", + " \"messages\": [\n", + " \"The package already arrived, but seems damaged\"\n", + " ]\n", + "}, config)\n", + "for message in response[\"messages\"]:\n", + " message.pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "================================ Event Id: 0000001753929112000#5996cf1c =================================\n", + "\n", + "================================ USER =================================\n", + "Hi, I'm having trouble with my order #12345\n", + "================================ ASSISTANT =================================\n", + "I'll help you look up the status of your order right away. Customer order 12345 was shipped 3 days ago, with an expected delivery tomorrow before 5pm. I can see that your order #12345 was shipped 3 days ago and is expected to be delivered tomorrow before 5 PM. Is there anything specific about the order that is causing you concern? I'm here to help you with any questions or issues you might be experiencing.\n" + ] + } + ], + "source": [ + "list_events()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}