mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
langgraph integration (#719)
This commit is contained in:
parent
60d6192f5d
commit
16da766fde
3 changed files with 428 additions and 0 deletions
|
|
@ -153,6 +153,7 @@
|
|||
"integrations/supermemory-sdk",
|
||||
"integrations/ai-sdk",
|
||||
"integrations/openai",
|
||||
"integrations/langgraph",
|
||||
"integrations/openai-agents-sdk",
|
||||
"integrations/mastra",
|
||||
"integrations/langchain",
|
||||
|
|
|
|||
1
apps/docs/images/langgraph.svg
Normal file
1
apps/docs/images/langgraph.svg
Normal file
|
|
@ -0,0 +1 @@
|
|||
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>LangGraph</title><path clip-rule="evenodd" d="M6.099 6H17.9C21.264 6 24 8.692 24 12s-2.736 6-6.099 6H6.1C2.736 18 0 15.308 0 12s2.736-6 6.099-6zm5.419 9.3c.148.154.367.146.561.106l.002.001c.09-.072-.038-.163-.16-.25-.074-.052-.145-.102-.166-.147.068-.08-.133-.265-.289-.408a1.52 1.52 0 01-.15-.148c-.11-.119-.155-.268-.2-.418-.03-.1-.06-.2-.11-.292-.304-.694-.653-1.383-1.143-1.97-.315-.39-.674-.74-1.033-1.09a19.384 19.384 0 01-.683-.688c-.226-.229-.362-.511-.499-.794-.114-.236-.228-.473-.396-.68-.507-.735-2.107-.936-2.342.104 0 .032-.01.052-.039.073-.13.094-.245.2-.342.327-.238.326-.274.877.022 1.17l.001-.019c.01-.147.02-.286.139-.391.228.193.576.262.841.117.32.45.422.995.525 1.54.085.456.17.912.382 1.316l.014.022c.124.203.25.41.41.587.059.089.178.184.297.279.157.125.314.25.329.359v.143c-.001.285-.002.58.184.813.103.205-.15.41-.352.385-.112.015-.233-.014-.354-.042-.165-.04-.329-.078-.462-.003-.038.04-.091.04-.145.042-.064.002-.129.004-.167.07-.008.019-.026.04-.045.063-.042.05-.087.105-.033.146l.015-.01c.082-.062.16-.12.27-.084-.014.08.039.102.092.123l.027.012a.344.344 0 01-.008.056c-.009.045-.017.088.018.127a.598.598 0 00.046-.054c.037-.046.073-.092.139-.11.144.19.289.111.471.013.206-.111.459-.248.81-.055-.135-.006-.255.01-.345.12-.023.024-.042.052-.002.084.207-.132.294-.085.375-.04.06.032.115.063.212.024l.07-.036c.155-.083.314-.166.499-.137-.139.039-.188.125-.242.218-.026.047-.054.095-.094.14-.021.021-.03.046-.007.08.29-.023.4-.095.548-.192.07-.046.15-.099.261-.154.124-.075.248-.027.368.02.13.05.255.098.371-.014.037-.033.083-.034.129-.034.016 0 .033 0 .05-.002-.037-.19-.24-.188-.448-.186-.24.003-.483.006-.475-.289.222-.149.224-.407.226-.651 0-.06 0-.117.005-.173.163.09.336.16.508.229.162.065.323.13.474.21.158.25.404.58.732.558.008-.026.016-.047.026-.073.019.004.039.008.059.014.086.02.178.044.223-.056zm6.429-2.829c.19.186.447.29.716.29.269 0 .526-.104.716-.29a.98.98 0 00.297-.7.98.98 0 00-.297-.7 1.024 1.024 0 00-1.08-.224l-.58-.831-.405.272.583.835a.978.978 0 00.05 1.348zm-1.817-2.69a1.03 1.03 0 001.056-.095.991.991 0 00.363-.507.97.97 0 00-.016-.62.994.994 0 00-.39-.488 1.028 1.028 0 00-1.298.14.987.987 0 00-.263.856.98.98 0 00.187.42c.095.125.218.225.36.294zm0 5.752a1.032 1.032 0 001.056-.095.991.991 0 00.363-.507.97.97 0 00-.016-.62.994.994 0 00-.39-.488 1.027 1.027 0 00-1.298.14.986.986 0 00-.263.856.98.98 0 00.187.42c.095.125.218.225.36.294zm.93-3.516v-.492h-1.55a.977.977 0 00-.217-.404l.584-.847-.425-.276-.583.847a1.023 1.023 0 00-1.047.23.973.973 0 00-.296.696c0 .261.107.512.296.696a1.023 1.023 0 001.047.23l.583.847.42-.276-.579-.847a.977.977 0 00.217-.404h1.55z"></path></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
426
apps/docs/integrations/langgraph.mdx
Normal file
426
apps/docs/integrations/langgraph.mdx
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
---
|
||||
title: "LangGraph"
|
||||
sidebarTitle: "LangGraph"
|
||||
description: "Add persistent memory to LangGraph agents with Supermemory"
|
||||
icon: "/images/langgraph.svg"
|
||||
---
|
||||
|
||||
Build stateful agents with LangGraph that remember context across sessions. Supermemory handles memory storage and retrieval while LangGraph manages your graph-based conversation flow.
|
||||
|
||||
## Overview
|
||||
|
||||
This guide shows how to integrate Supermemory with LangGraph to create agents that:
|
||||
|
||||
- Maintain user context through automatic profiling
|
||||
- Store and retrieve relevant memories at each node
|
||||
- Use conditional logic to decide what's worth remembering
|
||||
- Combine short-term (session) and long-term (cross-session) memory
|
||||
|
||||
## Setup
|
||||
|
||||
Install the required packages:
|
||||
|
||||
```bash
|
||||
pip install langgraph langchain-openai supermemory python-dotenv
|
||||
```
|
||||
|
||||
Configure your environment:
|
||||
|
||||
```bash
|
||||
# .env
|
||||
SUPERMEMORY_API_KEY=your-supermemory-api-key
|
||||
OPENAI_API_KEY=your-openai-api-key
|
||||
```
|
||||
|
||||
<Note>Get your Supermemory API key from [console.supermemory.ai](https://console.supermemory.ai).</Note>
|
||||
|
||||
## Basic integration
|
||||
|
||||
A minimal agent that fetches user context before responding and stores the conversation after:
|
||||
|
||||
```python
|
||||
from typing import Annotated, TypedDict
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.graph.message import add_messages
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import SystemMessage, HumanMessage
|
||||
from supermemory import Supermemory
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
llm = ChatOpenAI(model="gpt-4o")
|
||||
memory = Supermemory()
|
||||
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list, add_messages]
|
||||
user_id: str
|
||||
|
||||
def agent(state: State):
|
||||
user_id = state["user_id"]
|
||||
messages = state["messages"]
|
||||
user_query = messages[-1].content
|
||||
|
||||
# Fetch user profile with relevant memories
|
||||
profile_result = memory.profile(container_tag=user_id, q=user_query)
|
||||
|
||||
# Build context from profile
|
||||
static_facts = profile_result.profile.static or []
|
||||
dynamic_context = profile_result.profile.dynamic or []
|
||||
search_results = profile_result.search_results.results if profile_result.search_results else []
|
||||
|
||||
context = f"""
|
||||
User Background:
|
||||
{chr(10).join(static_facts) if static_facts else 'No profile yet.'}
|
||||
|
||||
Recent Context:
|
||||
{chr(10).join(dynamic_context) if dynamic_context else 'No recent activity.'}
|
||||
|
||||
Relevant Memories:
|
||||
{chr(10).join([r.memory or r.chunk for r in search_results]) if search_results else 'None found.'}
|
||||
"""
|
||||
|
||||
system = SystemMessage(content=f"You are a helpful assistant.\n\n{context}")
|
||||
response = llm.invoke([system] + messages)
|
||||
|
||||
# Store the interaction
|
||||
memory.add(
|
||||
content=f"User: {user_query}\nAssistant: {response.content}",
|
||||
container_tag=user_id
|
||||
)
|
||||
|
||||
return {"messages": [response]}
|
||||
|
||||
# Build the graph
|
||||
graph = StateGraph(State)
|
||||
graph.add_node("agent", agent)
|
||||
graph.add_edge(START, "agent")
|
||||
graph.add_edge("agent", END)
|
||||
app = graph.compile()
|
||||
|
||||
# Run it
|
||||
result = app.invoke({
|
||||
"messages": [HumanMessage(content="Hi! I'm working on a Python project.")],
|
||||
"user_id": "user_123"
|
||||
})
|
||||
print(result["messages"][-1].content)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core concepts
|
||||
|
||||
### User profiles
|
||||
|
||||
Supermemory automatically builds user profiles from stored memories:
|
||||
|
||||
- **Static facts**: Long-term information (preferences, expertise, background)
|
||||
- **Dynamic context**: Recent activity and current focus
|
||||
|
||||
```python
|
||||
result = memory.profile(
|
||||
container_tag="user_123",
|
||||
q="optional search query" # Also returns relevant memories
|
||||
)
|
||||
|
||||
print(result.profile.static) # ["User is a Python developer", "Prefers functional style"]
|
||||
print(result.profile.dynamic) # ["Working on async patterns", "Debugging rate limiting"]
|
||||
```
|
||||
|
||||
### Memory storage
|
||||
|
||||
Content you add gets processed into searchable memories:
|
||||
|
||||
```python
|
||||
# Store a conversation
|
||||
memory.add(
|
||||
content="User asked about graph traversal. Explained BFS vs DFS.",
|
||||
container_tag="user_123",
|
||||
metadata={"topic": "algorithms", "type": "conversation"}
|
||||
)
|
||||
|
||||
# Store a document
|
||||
memory.add(
|
||||
content="https://langchain-ai.github.io/langgraph/",
|
||||
container_tag="user_123"
|
||||
)
|
||||
```
|
||||
|
||||
### Memory search
|
||||
|
||||
Search returns both extracted memories and document chunks:
|
||||
|
||||
```python
|
||||
results = memory.search.memories(
|
||||
q="graph algorithms",
|
||||
container_tag="user_123",
|
||||
search_mode="hybrid",
|
||||
limit=5
|
||||
)
|
||||
|
||||
for r in results.results:
|
||||
print(r.memory or r.chunk, r.similarity)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete example: support agent
|
||||
|
||||
A support agent that learns from past tickets and adapts to each user's technical level:
|
||||
|
||||
```python
|
||||
from typing import Annotated, TypedDict, Optional
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
from langgraph.graph.message import add_messages
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_core.messages import SystemMessage, HumanMessage
|
||||
from supermemory import Supermemory
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
class SupportAgent:
|
||||
def __init__(self):
|
||||
self.llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
|
||||
self.memory = Supermemory()
|
||||
self.app = self._build_graph()
|
||||
|
||||
def _build_graph(self):
|
||||
class State(TypedDict):
|
||||
messages: Annotated[list, add_messages]
|
||||
user_id: str
|
||||
context: str
|
||||
category: Optional[str]
|
||||
|
||||
def retrieve_context(state: State):
|
||||
"""Fetch user profile and relevant past tickets."""
|
||||
user_id = state["user_id"]
|
||||
query = state["messages"][-1].content
|
||||
|
||||
result = self.memory.profile(
|
||||
container_tag=user_id,
|
||||
q=query,
|
||||
threshold=0.5
|
||||
)
|
||||
|
||||
static = result.profile.static or []
|
||||
dynamic = result.profile.dynamic or []
|
||||
memories = result.search_results.results if result.search_results else []
|
||||
|
||||
context = f"""
|
||||
## User Profile
|
||||
{chr(10).join(f"- {fact}" for fact in static) if static else "New user, no history."}
|
||||
|
||||
## Current Context
|
||||
{chr(10).join(f"- {ctx}" for ctx in dynamic) if dynamic else "No recent activity."}
|
||||
|
||||
## Related Past Tickets
|
||||
{chr(10).join(f"- {m.memory}" for m in memories[:3]) if memories else "No similar issues found."}
|
||||
"""
|
||||
return {"context": context}
|
||||
|
||||
def categorize(state: State):
|
||||
"""Determine ticket category for routing."""
|
||||
query = state["messages"][-1].content.lower()
|
||||
|
||||
if any(word in query for word in ["billing", "payment", "charge", "invoice"]):
|
||||
return {"category": "billing"}
|
||||
elif any(word in query for word in ["bug", "error", "broken", "crash"]):
|
||||
return {"category": "technical"}
|
||||
else:
|
||||
return {"category": "general"}
|
||||
|
||||
def respond(state: State):
|
||||
"""Generate a response using context."""
|
||||
category = state.get("category", "general")
|
||||
context = state.get("context", "")
|
||||
|
||||
system_prompt = f"""You are a support agent. Category: {category}
|
||||
|
||||
{context}
|
||||
|
||||
Guidelines:
|
||||
- Match explanation depth to the user's technical level
|
||||
- Reference past interactions when relevant
|
||||
- Be direct and helpful"""
|
||||
|
||||
system = SystemMessage(content=system_prompt)
|
||||
response = self.llm.invoke([system] + state["messages"])
|
||||
|
||||
return {"messages": [response]}
|
||||
|
||||
def store_interaction(state: State):
|
||||
"""Save the ticket for future context."""
|
||||
user_msg = state["messages"][-2].content
|
||||
ai_msg = state["messages"][-1].content
|
||||
category = state.get("category", "general")
|
||||
|
||||
self.memory.add(
|
||||
content=f"Support ticket ({category}): {user_msg}\nResolution: {ai_msg[:300]}",
|
||||
container_tag=state["user_id"],
|
||||
metadata={"type": "support_ticket", "category": category}
|
||||
)
|
||||
|
||||
return {}
|
||||
|
||||
# Build the graph
|
||||
graph = StateGraph(State)
|
||||
graph.add_node("retrieve", retrieve_context)
|
||||
graph.add_node("categorize", categorize)
|
||||
graph.add_node("respond", respond)
|
||||
graph.add_node("store", store_interaction)
|
||||
|
||||
graph.add_edge(START, "retrieve")
|
||||
graph.add_edge("retrieve", "categorize")
|
||||
graph.add_edge("categorize", "respond")
|
||||
graph.add_edge("respond", "store")
|
||||
graph.add_edge("store", END)
|
||||
|
||||
checkpointer = MemorySaver()
|
||||
return graph.compile(checkpointer=checkpointer)
|
||||
|
||||
def handle(self, user_id: str, message: str, thread_id: str) -> str:
|
||||
"""Process a support request."""
|
||||
config = {"configurable": {"thread_id": thread_id}}
|
||||
|
||||
result = self.app.invoke(
|
||||
{"messages": [HumanMessage(content=message)], "user_id": user_id},
|
||||
config=config
|
||||
)
|
||||
|
||||
return result["messages"][-1].content
|
||||
|
||||
|
||||
# Usage
|
||||
if __name__ == "__main__":
|
||||
agent = SupportAgent()
|
||||
|
||||
# First interaction
|
||||
response = agent.handle(
|
||||
user_id="customer_alice",
|
||||
message="The API is returning 429 errors when I make requests",
|
||||
thread_id="ticket_001"
|
||||
)
|
||||
print(response)
|
||||
|
||||
# Follow-up (agent remembers context)
|
||||
response = agent.handle(
|
||||
user_id="customer_alice",
|
||||
message="I'm only making 10 requests per minute though",
|
||||
thread_id="ticket_001"
|
||||
)
|
||||
print(response)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Advanced patterns
|
||||
|
||||
### Conditional memory storage
|
||||
|
||||
Not everything is worth remembering. Use conditional edges to filter:
|
||||
|
||||
```python
|
||||
def should_store(state: State) -> str:
|
||||
"""Skip storing trivial messages."""
|
||||
last_msg = state["messages"][-1].content.lower()
|
||||
|
||||
skip_phrases = ["thanks", "ok", "got it", "bye"]
|
||||
if len(last_msg) < 20 or any(p in last_msg for p in skip_phrases):
|
||||
return "skip"
|
||||
return "store"
|
||||
|
||||
graph.add_conditional_edges("respond", should_store, {
|
||||
"store": "store",
|
||||
"skip": END
|
||||
})
|
||||
```
|
||||
|
||||
### Parallel memory operations
|
||||
|
||||
Fetch memories and categorize at the same time:
|
||||
|
||||
```python
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
|
||||
graph = StateGraph(State)
|
||||
graph.add_node("retrieve", retrieve_context)
|
||||
graph.add_node("categorize", categorize)
|
||||
graph.add_node("respond", respond)
|
||||
|
||||
# Both run in parallel after START
|
||||
graph.add_edge(START, "retrieve")
|
||||
graph.add_edge(START, "categorize")
|
||||
|
||||
# Both must complete before respond
|
||||
graph.add_edge("retrieve", "respond")
|
||||
graph.add_edge("categorize", "respond")
|
||||
graph.add_edge("respond", END)
|
||||
```
|
||||
|
||||
### Metadata filtering
|
||||
|
||||
Organize memories by project, topic, or any custom field:
|
||||
|
||||
```python
|
||||
# Store with metadata
|
||||
memory.add(
|
||||
content="User prefers detailed error messages with stack traces",
|
||||
container_tag="user_123",
|
||||
metadata={
|
||||
"type": "preference",
|
||||
"project": "api-v2",
|
||||
"priority": "high"
|
||||
}
|
||||
)
|
||||
|
||||
# Search with filters
|
||||
results = memory.search.memories(
|
||||
q="error handling preferences",
|
||||
container_tag="user_123",
|
||||
filters={
|
||||
"AND": [
|
||||
{"key": "type", "value": "preference"},
|
||||
{"key": "project", "value": "api-v2"}
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Combining session and long-term memory
|
||||
|
||||
LangGraph's checkpointer handles within-session state. Supermemory handles cross-session memory. Use both:
|
||||
|
||||
```python
|
||||
from langgraph.checkpoint.memory import MemorySaver
|
||||
|
||||
# Session memory (cleared when thread ends)
|
||||
checkpointer = MemorySaver()
|
||||
app = graph.compile(checkpointer=checkpointer)
|
||||
|
||||
# Long-term memory (persists across sessions)
|
||||
# Handled by Supermemory in your nodes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="User profiles" icon="user" href="/user-profiles">
|
||||
Deep dive into automatic user profiling
|
||||
</Card>
|
||||
|
||||
<Card title="Search API" icon="search" href="/search">
|
||||
Advanced search patterns and filtering
|
||||
</Card>
|
||||
|
||||
<Card title="OpenAI SDK" icon="message-bot" href="/integrations/openai">
|
||||
Native OpenAI integration with memory tools
|
||||
</Card>
|
||||
|
||||
<Card title="AI SDK" icon="triangle" href="/integrations/ai-sdk">
|
||||
Memory middleware for Next.js apps
|
||||
</Card>
|
||||
</CardGroup>
|
||||
Loading…
Add table
Reference in a new issue