mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-09-09 11:17:53 +02:00
Merge branch 'main' into wfh/docs/memstoreconcept
This commit is contained in:
@@ -13,7 +13,7 @@ serve-clean-docs: clean-docs
|
||||
poetry run python -m mkdocs serve -c -f docs/mkdocs.yml --strict -w ./libs/langgraph
|
||||
|
||||
serve-docs: build-typedoc
|
||||
poetry run python -m mkdocs serve -f docs/mkdocs.yml -w ./libs/langgraph --dirty
|
||||
poetry run python -m mkdocs serve -f docs/mkdocs.yml -w ./libs/langgraph -w ./libs/checkpoint --dirty
|
||||
|
||||
clean-docs:
|
||||
find ./docs/docs -name "*.ipynb" -type f -delete
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,424 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to add semantic search to your agent's memory\n",
|
||||
"\n",
|
||||
"This guide shows how to enable semantic search in your agent's memory store. This lets search for items in the store by semantic similarity.\n",
|
||||
"\n",
|
||||
"First, install this guide's prerequisites."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph langchain-openai langchain"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import getpass\n",
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def _set_env(var: str):\n",
|
||||
" if not os.environ.get(var):\n",
|
||||
" os.environ[var] = getpass.getpass(f\"{var}: \")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"_set_env(\"OPENAI_API_KEY\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, create the store."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.embeddings import init_embeddings\n",
|
||||
"from langgraph.store.memory import InMemoryStore\n",
|
||||
"\n",
|
||||
"# Create store with semantic search enabled\n",
|
||||
"embeddings = init_embeddings(\"openai:text-embedding-3-small\")\n",
|
||||
"store = InMemoryStore(\n",
|
||||
" index={\n",
|
||||
" \"embed\": embeddings,\n",
|
||||
" \"dims\": 1536,\n",
|
||||
" }\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now let's store some memories:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 26,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Store some memories\n",
|
||||
"store.put((\"user_123\", \"memories\"), \"1\", {\"text\": \"I love pizza\"})\n",
|
||||
"store.put((\"user_123\", \"memories\"), \"2\", {\"text\": \"I prefer Italian food\"})\n",
|
||||
"store.put((\"user_123\", \"memories\"), \"3\", {\"text\": \"I don't like spicy food\"})\n",
|
||||
"store.put((\"user_123\", \"memories\"), \"3\", {\"text\": \"I am studying econometrics\"})\n",
|
||||
"store.put((\"user_123\", \"memories\"), \"3\", {\"text\": \"I am a plumber\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Search memories using natural language:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 27,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Memory: I prefer Italian food (similarity: 0.46482669521168163)\n",
|
||||
"Memory: I love pizza (similarity: 0.35514845174380766)\n",
|
||||
"Memory: I am a plumber (similarity: 0.155698702336571)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Find memories about food preferences\n",
|
||||
"memories = store.search((\"user_123\", \"memories\"), query=\"I like food?\", limit=5)\n",
|
||||
"\n",
|
||||
"for memory in memories:\n",
|
||||
" print(f'Memory: {memory.value[\"text\"]} (similarity: {memory.score})')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Using in your agent\n",
|
||||
"\n",
|
||||
"Add semantic search to any node by injecting the store:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 40,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import uuid\n",
|
||||
"from typing import Optional\n",
|
||||
"\n",
|
||||
"from langchain.chat_models import init_chat_model\n",
|
||||
"from langchain_core.tools import InjectedToolArg\n",
|
||||
"from langgraph.store.base import BaseStore\n",
|
||||
"from typing_extensions import Annotated\n",
|
||||
"\n",
|
||||
"from langgraph.prebuilt import create_react_agent\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def add_memories(state, *, store: BaseStore):\n",
|
||||
" # Search based on user's last message\n",
|
||||
" items = store.search(\n",
|
||||
" (\"user_123\", \"memories\"), query=state[\"messages\"][-1].content, limit=2\n",
|
||||
" )\n",
|
||||
" memories = \"\\n\".join(item.value[\"text\"] for item in items)\n",
|
||||
" memories = f\"## Memories of user\\n{memories}\" if memories else \"\"\n",
|
||||
" return [\n",
|
||||
" {\"role\": \"system\", \"content\": f\"You are a helpful assistant.\\n{memories}\"}\n",
|
||||
" ] + state[\"messages\"]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def upsert_memory(\n",
|
||||
" content: str,\n",
|
||||
" *,\n",
|
||||
" memory_id: Optional[uuid.UUID] = None,\n",
|
||||
" store: Annotated[BaseStore, InjectedToolArg],\n",
|
||||
"):\n",
|
||||
" \"\"\"Upsert a memory in the database.\"\"\"\n",
|
||||
" mem_id = memory_id or uuid.uuid4()\n",
|
||||
" store.put(\n",
|
||||
" (\"user_123\", \"memories\"),\n",
|
||||
" key=str(mem_id),\n",
|
||||
" value={\"text\": content},\n",
|
||||
" )\n",
|
||||
" return f\"Stored memory {mem_id}\"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"agent = create_react_agent(\n",
|
||||
" init_chat_model(\"openai:gpt-4o-mini\"),\n",
|
||||
" tools=[upsert_memory],\n",
|
||||
" state_modifier=add_memories,\n",
|
||||
" store=store,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 44,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"What are you in the mood for? Since you love Italian food and pizza, would you like some recommendations for a delicious pizza or a different Italian dish?"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"async for message, metadata in agent.astream(\n",
|
||||
" input={\"messages\": [{\"role\": \"user\", \"content\": \"I'm hungry\"}]},\n",
|
||||
" stream_mode=\"messages\",\n",
|
||||
"):\n",
|
||||
" print(message.content, end=\"\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Advanced Usage\n",
|
||||
"\n",
|
||||
"#### Multi-vector indexing\n",
|
||||
"\n",
|
||||
"Store and search different aspects of memories separately to improve recall or omit certain fields from being indexed."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Configure store to embed both memory content and emotional context\n",
|
||||
"store = InMemoryStore(\n",
|
||||
" index={\"embed\": embeddings, \"dims\": 1536, \"fields\": [\"memory\", \"emotional_context\"]}\n",
|
||||
")\n",
|
||||
"# Store memories with different content/emotion pairs\n",
|
||||
"store.put(\n",
|
||||
" (\"user_123\", \"memories\"),\n",
|
||||
" \"mem1\",\n",
|
||||
" {\n",
|
||||
" \"memory\": \"Had pizza with friends at Mario's\",\n",
|
||||
" \"emotional_context\": \"felt happy and connected\",\n",
|
||||
" \"this_isnt_indexed\": \"I prefer ravioli though\",\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"store.put(\n",
|
||||
" (\"user_123\", \"memories\"),\n",
|
||||
" \"mem2\",\n",
|
||||
" {\n",
|
||||
" \"memory\": \"Ate alone at home\",\n",
|
||||
" \"emotional_context\": \"felt a bit lonely\",\n",
|
||||
" \"this_isnt_indexed\": \"I like pie\",\n",
|
||||
" },\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Search focusing on emotional state - matches mem2\n",
|
||||
"results = store.search(\n",
|
||||
" (\"user_123\", \"memories\"), query=\"times they felt isolated\", limit=1\n",
|
||||
")\n",
|
||||
"print(\"Expect mem 2\")\n",
|
||||
"for r in results:\n",
|
||||
" print(f\"Item: {r.key}; Score ({r.score})\")\n",
|
||||
" print(f\"Memory: {r.value['memory']}\")\n",
|
||||
" print(f\"Emotion: {r.value['emotional_context']}\\n\")\n",
|
||||
"\n",
|
||||
"# Search focusing on social eating - matches mem1\n",
|
||||
"print(\"Expect mem1\")\n",
|
||||
"results = store.search((\"user_123\", \"memories\"), query=\"fun pizza\", limit=1)\n",
|
||||
"for r in results:\n",
|
||||
" print(f\"Item: {r.key}; Score ({r.score})\")\n",
|
||||
" print(f\"Memory: {r.value['memory']}\")\n",
|
||||
" print(f\"Emotion: {r.value['emotional_context']}\\n\")\n",
|
||||
"\n",
|
||||
"print(\"Expect random lower score (ravioli not indexed)\")\n",
|
||||
"results = store.search((\"user_123\", \"memories\"), query=\"ravioli\", limit=1)\n",
|
||||
"for r in results:\n",
|
||||
" print(f\"Item: {r.key}; Score ({r.score})\")\n",
|
||||
" print(f\"Memory: {r.value['memory']}\")\n",
|
||||
" print(f\"Emotion: {r.value['emotional_context']}\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Override fields at storage time\n",
|
||||
"You can override which fields to embed when storing a specific memory using `put(..., index=[...fields])`, regardless of the store's default configuration."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 57,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Expect mem1\n",
|
||||
"Item: mem1; Score (0.3374698138722726)\n",
|
||||
"Memory: I love spicy food\n",
|
||||
"Context: At a Thai restaurant\n",
|
||||
"\n",
|
||||
"Expect mem2\n",
|
||||
"Item: mem2; Score (0.3679447999059255)\n",
|
||||
"Memory: The restaurant was too loud\n",
|
||||
"Context: Dinner at an Italian place\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"embeddings = init_embeddings(\"openai:text-embedding-3-small\")\n",
|
||||
"store = InMemoryStore(\n",
|
||||
" index={\n",
|
||||
" \"embed\": embeddings,\n",
|
||||
" \"dims\": 1536,\n",
|
||||
" \"fields\": [\"memory\"],\n",
|
||||
" } # Default to embed memory field\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Store one memory with default indexing\n",
|
||||
"store.put(\n",
|
||||
" (\"user_123\", \"memories\"),\n",
|
||||
" \"mem1\",\n",
|
||||
" {\"memory\": \"I love spicy food\", \"context\": \"At a Thai restaurant\"},\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Store another overriding which fields to embed\n",
|
||||
"store.put(\n",
|
||||
" (\"user_123\", \"memories\"),\n",
|
||||
" \"mem2\",\n",
|
||||
" {\"memory\": \"The restaurant was too loud\", \"context\": \"Dinner at an Italian place\"},\n",
|
||||
" index=[\"context\"], # Override: only embed the context\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Search about food - matches mem1 (using default field)\n",
|
||||
"print(\"Expect mem1\")\n",
|
||||
"results = store.search(\n",
|
||||
" (\"user_123\", \"memories\"), query=\"what food do they like\", limit=1\n",
|
||||
")\n",
|
||||
"for r in results:\n",
|
||||
" print(f\"Item: {r.key}; Score ({r.score})\")\n",
|
||||
" print(f\"Memory: {r.value['memory']}\")\n",
|
||||
" print(f\"Context: {r.value['context']}\\n\")\n",
|
||||
"\n",
|
||||
"# Search about restaurant atmosphere - matches mem2 (using overridden field)\n",
|
||||
"print(\"Expect mem2\")\n",
|
||||
"results = store.search(\n",
|
||||
" (\"user_123\", \"memories\"), query=\"restaurant environment\", limit=1\n",
|
||||
")\n",
|
||||
"for r in results:\n",
|
||||
" print(f\"Item: {r.key}; Score ({r.score})\")\n",
|
||||
" print(f\"Memory: {r.value['memory']}\")\n",
|
||||
" print(f\"Context: {r.value['context']}\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Disable Indexing for Specific Memories\n",
|
||||
"\n",
|
||||
"Some memories shouldn't be searchable by content. You can disable indexing for these while still storing them using \n",
|
||||
"`put(..., index=False)`. Example:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"store = InMemoryStore(index={\"embed\": embeddings, \"dims\": 1536, \"fields\": [\"memory\"]})\n",
|
||||
"\n",
|
||||
"# Store a normal indexed memory\n",
|
||||
"store.put(\n",
|
||||
" (\"user_123\", \"memories\"),\n",
|
||||
" \"mem1\",\n",
|
||||
" {\"memory\": \"I love chocolate ice cream\", \"type\": \"preference\"},\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Store a system memory without indexing\n",
|
||||
"store.put(\n",
|
||||
" (\"user_123\", \"memories\"),\n",
|
||||
" \"mem2\",\n",
|
||||
" {\"memory\": \"User completed onboarding\", \"type\": \"system\"},\n",
|
||||
" index=False, # Disable indexing entirely\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Search about food preferences - finds mem1\n",
|
||||
"print(\"Expect mem1\")\n",
|
||||
"results = store.search((\"user_123\", \"memories\"), query=\"what food preferences\", limit=1)\n",
|
||||
"for r in results:\n",
|
||||
" print(f\"Item: {r.key}; Score ({r.score})\")\n",
|
||||
" print(f\"Memory: {r.value['memory']}\")\n",
|
||||
" print(f\"Type: {r.value['type']}\\n\")\n",
|
||||
"\n",
|
||||
"# Search about onboarding - won't find mem2 (not indexed)\n",
|
||||
"print(\"Expect low score (mem2 not indexed)\")\n",
|
||||
"results = store.search((\"user_123\", \"memories\"), query=\"onboarding status\", limit=1)\n",
|
||||
"for r in results:\n",
|
||||
" print(f\"Item: {r.key}; Score ({r.score})\")\n",
|
||||
" print(f\"Memory: {r.value['memory']}\")\n",
|
||||
" print(f\"Type: {r.value['type']}\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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.11.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
@@ -164,6 +164,7 @@ nav:
|
||||
- how-tos/memory/manage-conversation-history.ipynb
|
||||
- how-tos/memory/delete-messages.ipynb
|
||||
- how-tos/memory/add-summary-conversation-history.ipynb
|
||||
- how-tos/memory/semantic-search.ipynb
|
||||
- Human-in-the-loop:
|
||||
- Human-in-the-loop: how-tos#human-in-the-loop
|
||||
- how-tos/human_in_the_loop/breakpoints.ipynb
|
||||
@@ -225,6 +226,7 @@ nav:
|
||||
- cloud/deployment/setup.md
|
||||
- cloud/deployment/setup_pyproject.md
|
||||
- cloud/deployment/setup_javascript.md
|
||||
- cloud/deployment/semantic_search.md
|
||||
- cloud/deployment/custom_docker.md
|
||||
- cloud/deployment/test_locally.md
|
||||
- cloud/deployment/graph_rebuild.md
|
||||
|
||||
@@ -72,6 +72,8 @@ class AsyncPostgresStore(AsyncBatchedBaseStore, BasePostgresStore[_ainternal.Con
|
||||
# Store documents
|
||||
await store.aput(("docs",), "doc1", {"text": "Python tutorial"})
|
||||
await store.aput(("docs",), "doc2", {"text": "TypeScript guide"})
|
||||
# Don't index the following
|
||||
await store.aput(("docs",), "doc3", {"text": "Other guide"}, index=False)
|
||||
|
||||
# Search by similarity
|
||||
results = await store.asearch(("docs",), query="python programming")
|
||||
|
||||
@@ -569,6 +569,7 @@ class PostgresStore(BaseStore, BasePostgresStore[_pg_internal.Conn]):
|
||||
# Store documents
|
||||
store.put(("docs",), "doc1", {"text": "Python tutorial"})
|
||||
store.put(("docs",), "doc2", {"text": "TypeScript guide"})
|
||||
store.put(("docs",), "doc2", {"text": "Other guide"}, index=False) # don't index
|
||||
|
||||
# Search by similarity
|
||||
results = store.search(("docs",), query="python programming")
|
||||
|
||||
@@ -133,7 +133,7 @@ class GetOp(NamedTuple):
|
||||
This operation allows precise retrieval of stored items using their full path
|
||||
(namespace) and unique identifier (key) combination.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
|
||||
Basic item retrieval:
|
||||
```python
|
||||
@@ -145,7 +145,7 @@ class GetOp(NamedTuple):
|
||||
namespace: tuple[str, ...]
|
||||
"""Hierarchical path that uniquely identifies the item's location.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
|
||||
```python
|
||||
("users",) # Root level users namespace
|
||||
@@ -156,7 +156,7 @@ class GetOp(NamedTuple):
|
||||
key: str
|
||||
"""Unique identifier for the item within its specific namespace.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
|
||||
```python
|
||||
"user123" # For a user profile
|
||||
@@ -175,7 +175,7 @@ class SearchOp(NamedTuple):
|
||||
Note:
|
||||
Natural language search support depends on your store implementation.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
Search with filters and pagination:
|
||||
```python
|
||||
SearchOp(
|
||||
@@ -199,7 +199,7 @@ class SearchOp(NamedTuple):
|
||||
namespace_prefix: tuple[str, ...]
|
||||
"""Hierarchical path prefix defining the search scope.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
|
||||
```python
|
||||
() # Search entire store
|
||||
@@ -221,7 +221,7 @@ class SearchOp(NamedTuple):
|
||||
- $lt: Less than
|
||||
- $lte: Less than or equal to
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
Simple exact match:
|
||||
|
||||
```python
|
||||
@@ -253,7 +253,7 @@ class SearchOp(NamedTuple):
|
||||
query: Optional[str] = None
|
||||
"""Natural language search query for semantic search capabilities.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
- "technical documentation about REST APIs"
|
||||
- "machine learning papers from 2023"
|
||||
"""
|
||||
@@ -263,7 +263,7 @@ class SearchOp(NamedTuple):
|
||||
NamespacePath = tuple[Union[str, Literal["*"]], ...]
|
||||
"""A tuple representing a namespace path that can include wildcards.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
```python
|
||||
("users",) # Exact users namespace
|
||||
("documents", "*") # Any sub-namespace under documents
|
||||
@@ -288,7 +288,7 @@ class MatchCondition(NamedTuple):
|
||||
pattern that can include wildcards to flexibly match different namespace
|
||||
hierarchies.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
Prefix matching:
|
||||
```python
|
||||
MatchCondition(match_type="prefix", path=("users", "profiles"))
|
||||
@@ -318,7 +318,7 @@ class ListNamespacesOp(NamedTuple):
|
||||
This operation allows exploring the organization of data, finding specific
|
||||
collections, and navigating the namespace hierarchy.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
|
||||
List all namespaces under the "documents" path:
|
||||
```python
|
||||
@@ -341,7 +341,7 @@ class ListNamespacesOp(NamedTuple):
|
||||
match_conditions: Optional[tuple[MatchCondition, ...]] = None
|
||||
"""Optional conditions for filtering namespaces.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
All user namespaces:
|
||||
```python
|
||||
(MatchCondition(match_type="prefix", path=("users",)),)
|
||||
@@ -383,7 +383,7 @@ class PutOp(NamedTuple):
|
||||
The namespace acts as a folder-like structure to organize items.
|
||||
Each element in the tuple represents one level in the hierarchy.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
Root level documents
|
||||
```python
|
||||
("documents",)
|
||||
@@ -429,9 +429,9 @@ class PutOp(NamedTuple):
|
||||
"""Controls how the item's fields are indexed for search operations.
|
||||
|
||||
Indexing configuration determines how the item can be found through search:
|
||||
- None (default): Uses the store's default indexing configuration (if provided)
|
||||
- False: Disables indexing for this item
|
||||
- list[str]: Specifies which json path fields to index for search
|
||||
- None (default): Uses the store's default indexing configuration (if provided)
|
||||
- False: Disables indexing for this item
|
||||
- list[str]: Specifies which json path fields to index for search
|
||||
|
||||
The item remains accessible through direct get() operations regardless of indexing.
|
||||
When indexed, fields can be searched using natural language queries through
|
||||
@@ -445,15 +445,14 @@ class PutOp(NamedTuple):
|
||||
- Last element: "array[-1]"
|
||||
- All elements (each individually): "array[*]"
|
||||
|
||||
???+example "Examples"
|
||||
- None - Use store defaults
|
||||
- False - Don't index this item
|
||||
???+ example "Examples"
|
||||
- None - Use store defaults (whole item)
|
||||
- list[str] - List of fields to index
|
||||
|
||||
```python
|
||||
[
|
||||
"metadata.title", # Nested field access
|
||||
"chapters[*].content", # Index content from all chapters as separate vectors
|
||||
"context[*].content", # Index content from all context as separate vectors
|
||||
"authors[0].name", # First author's name
|
||||
"revisions[-1].changes", # Most recent revision's changes
|
||||
"sections[*].paragraphs[*].text", # All text from all paragraphs in all sections
|
||||
@@ -495,7 +494,7 @@ class IndexConfig(TypedDict, total=False):
|
||||
2. A synchronous embedding function (EmbeddingsFunc)
|
||||
3. An asynchronous embedding function (AEmbeddingsFunc)
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
Using LangChain's initialization with InMemoryStore:
|
||||
```python
|
||||
from langchain.embeddings import init_embeddings
|
||||
@@ -557,7 +556,37 @@ class IndexConfig(TypedDict, total=False):
|
||||
fields: Optional[list[str]]
|
||||
"""Fields to extract text from for embedding generation.
|
||||
|
||||
Defaults to the root ["$"], which embeds the json object as a whole.
|
||||
Controls which parts of stored items are embedded for semantic search. Follows JSON path syntax:
|
||||
|
||||
- ["$"]: Embeds the entire JSON object as one vector (default)
|
||||
- ["field1", "field2"]: Embeds specific top-level fields
|
||||
- ["parent.child"]: Embeds nested fields using dot notation
|
||||
- ["array[*].field"]: Embeds field from each array element separately
|
||||
|
||||
Note:
|
||||
You can always override this behavior when storing an item using the
|
||||
`index` parameter in the `put` or `aput` operations.
|
||||
|
||||
???+ example "Examples"
|
||||
```python
|
||||
# Embed entire document (default)
|
||||
fields=["$"]
|
||||
|
||||
# Embed specific fields
|
||||
fields=["text", "summary"]
|
||||
|
||||
# Embed nested fields
|
||||
fields=["metadata.title", "content.body"]
|
||||
|
||||
# Embed from arrays
|
||||
fields=["messages[*].content"] # Each message content separately
|
||||
fields=["context[0].text"] # First context item's text
|
||||
```
|
||||
|
||||
Note:
|
||||
- Fields missing from a document are skipped
|
||||
- Array notation creates separate embeddings for each element
|
||||
- Complex nested paths are supported (e.g., "a.b[*].c.d")
|
||||
"""
|
||||
|
||||
|
||||
@@ -645,7 +674,7 @@ class BaseStore(ABC):
|
||||
index={
|
||||
"dims": 1536, # embedding dimensions
|
||||
"embed": your_embedding_function, # function to create embeddings
|
||||
"fields": ["text"] # fields to embed
|
||||
"fields": ["text"] # fields to embed. Defaults to ["$"]
|
||||
}
|
||||
)
|
||||
|
||||
@@ -680,7 +709,10 @@ class BaseStore(ABC):
|
||||
value: Dictionary containing the item's data. Must contain string keys
|
||||
and JSON-serializable values.
|
||||
index: Controls how the item's fields are indexed for search:
|
||||
- None (default): Use store's default indexing configuration
|
||||
|
||||
- None (default): Use `fields` you configured when creating the store (if any)
|
||||
If you do not initialize the store with indexing capabilities,
|
||||
the `index` parameter will be ignored
|
||||
- False: Disable indexing for this item
|
||||
- list[str]: List of field paths to index, supporting:
|
||||
- Nested fields: "metadata.title"
|
||||
@@ -688,23 +720,25 @@ class BaseStore(ABC):
|
||||
- Specific indices: "authors[0].name"
|
||||
|
||||
Note:
|
||||
Indexing capabilities depend on your store implementation.
|
||||
Some implementations may support only a subset of indexing features.
|
||||
Indexing support depends on your store implementation.
|
||||
If you do not initialize the store with indexing capabilities,
|
||||
the `index` parameter will be ignored.
|
||||
|
||||
???+example "Examples"
|
||||
Simple storage without special indexing (respects store defaults)
|
||||
???+ example "Examples"
|
||||
Store item. Indexing depends on how you configure the store.
|
||||
```python
|
||||
store.put(("docs",), "report", {"title": "Annual Report"})
|
||||
store.put(("docs",), "report", {"memory": "Will likes ai"})
|
||||
```
|
||||
|
||||
Index specific fields for search (if store configured to index items)
|
||||
Do not index item for semantic search. Still accessible through get()
|
||||
and search() operations but won't have a vector representation.
|
||||
```python
|
||||
store.put(("docs",), "report", {"title": "Annual Report"}, index=["title"])
|
||||
store.put(("docs",), "report", {"memory": "Will likes ai"}, index=False)
|
||||
```
|
||||
|
||||
Do not index for semantic search
|
||||
Index specific fields for search.
|
||||
```python
|
||||
store.put(("docs",), "report", {"title": "Annual Report"}, index=False)
|
||||
store.put(("docs",), "report", {"memory": "Will likes ai"}, index=["memory"])
|
||||
```
|
||||
"""
|
||||
_validate_namespace(namespace)
|
||||
@@ -745,7 +779,7 @@ class BaseStore(ABC):
|
||||
List[Tuple[str, ...]]: A list of namespace tuples that match the criteria.
|
||||
Each tuple represents a full namespace path up to `max_depth`.
|
||||
|
||||
???+example "Examples":
|
||||
???+ example "Examples":
|
||||
Setting max_depth=3. Given the namespaces:
|
||||
```python
|
||||
# Example if you have the following namespaces:
|
||||
@@ -862,7 +896,10 @@ class BaseStore(ABC):
|
||||
value: Dictionary containing the item's data. Must contain string keys
|
||||
and JSON-serializable values.
|
||||
index: Controls how the item's fields are indexed for search:
|
||||
- None (default): Use store's default indexing configuration
|
||||
|
||||
- None (default): Use `fields` you configured when creating the store (if any)
|
||||
If you do not initialize the store with indexing capabilities,
|
||||
the `index` parameter will be ignored
|
||||
- False: Disable indexing for this item
|
||||
- list[str]: List of field paths to index, supporting:
|
||||
- Nested fields: "metadata.title"
|
||||
@@ -870,13 +907,20 @@ class BaseStore(ABC):
|
||||
- Specific indices: "authors[0].name"
|
||||
|
||||
Note:
|
||||
Indexing capabilities depend on your store implementation.
|
||||
Some implementations may support only a subset of indexing features.
|
||||
Indexing support depends on your store implementation.
|
||||
If you do not initialize the store with indexing capabilities,
|
||||
the `index` parameter will be ignored.
|
||||
|
||||
???+example "Examples"
|
||||
Simple storage without special indexing:
|
||||
???+ example "Examples"
|
||||
Store item. Indexing depends on how you configure the store.
|
||||
```python
|
||||
await store.aput(("docs",), "report", {"title": "Annual Report"})
|
||||
await store.aput(("docs",), "report", {"memory": "Will likes ai"})
|
||||
```
|
||||
|
||||
Do not index item for semantic search. Still accessible through get()
|
||||
and search() operations but won't have a vector representation.
|
||||
```python
|
||||
await store.aput(("docs",), "report", {"memory": "Will likes ai"}, index=False)
|
||||
```
|
||||
|
||||
Index specific fields for search (if store configured to index items):
|
||||
@@ -885,10 +929,10 @@ class BaseStore(ABC):
|
||||
("docs",),
|
||||
"report",
|
||||
{
|
||||
"title": "Q4 Report",
|
||||
"chapters": [{"content": "..."}, {"content": "..."}]
|
||||
"memory": "Will likes ai",
|
||||
"context": [{"content": "..."}, {"content": "..."}]
|
||||
},
|
||||
index=["title", "chapters[*].content"]
|
||||
index=["memory", "context[*].content"]
|
||||
)
|
||||
```
|
||||
"""
|
||||
@@ -930,7 +974,7 @@ class BaseStore(ABC):
|
||||
List[Tuple[str, ...]]: A list of namespace tuples that match the criteria.
|
||||
Each tuple represents a full namespace path up to `max_depth`.
|
||||
|
||||
???+example "Examples"
|
||||
???+ example "Examples"
|
||||
Setting max_depth=3 with existing namespaces:
|
||||
```python
|
||||
# Given the following namespaces:
|
||||
|
||||
@@ -673,7 +673,7 @@ class Pregel(PregelProtocol):
|
||||
self, config: RunnableConfig, *, subgraphs: bool = False
|
||||
) -> StateSnapshot:
|
||||
"""Get the current state of the graph."""
|
||||
checkpointer: Optional[BaseCheckpointSaver] = config[CONF].get(
|
||||
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
|
||||
CONFIG_KEY_CHECKPOINTER, self.checkpointer
|
||||
)
|
||||
if not checkpointer:
|
||||
@@ -710,7 +710,7 @@ class Pregel(PregelProtocol):
|
||||
self, config: RunnableConfig, *, subgraphs: bool = False
|
||||
) -> StateSnapshot:
|
||||
"""Get the current state of the graph."""
|
||||
checkpointer: Optional[BaseCheckpointSaver] = config[CONF].get(
|
||||
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
|
||||
CONFIG_KEY_CHECKPOINTER, self.checkpointer
|
||||
)
|
||||
if not checkpointer:
|
||||
@@ -751,8 +751,9 @@ class Pregel(PregelProtocol):
|
||||
before: Optional[RunnableConfig] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> Iterator[StateSnapshot]:
|
||||
config = ensure_config(config)
|
||||
"""Get the history of the state of the graph."""
|
||||
checkpointer: Optional[BaseCheckpointSaver] = config[CONF].get(
|
||||
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
|
||||
CONFIG_KEY_CHECKPOINTER, self.checkpointer
|
||||
)
|
||||
if not checkpointer:
|
||||
@@ -800,8 +801,9 @@ class Pregel(PregelProtocol):
|
||||
before: Optional[RunnableConfig] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> AsyncIterator[StateSnapshot]:
|
||||
config = ensure_config(config)
|
||||
"""Get the history of the state of the graph."""
|
||||
checkpointer: Optional[BaseCheckpointSaver] = config[CONF].get(
|
||||
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
|
||||
CONFIG_KEY_CHECKPOINTER, self.checkpointer
|
||||
)
|
||||
if not checkpointer:
|
||||
@@ -855,7 +857,7 @@ class Pregel(PregelProtocol):
|
||||
node `as_node`. If `as_node` is not provided, it will be set to the last node
|
||||
that updated the state, if not ambiguous.
|
||||
"""
|
||||
checkpointer: Optional[BaseCheckpointSaver] = config[CONF].get(
|
||||
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
|
||||
CONFIG_KEY_CHECKPOINTER, self.checkpointer
|
||||
)
|
||||
if not checkpointer:
|
||||
@@ -1130,7 +1132,7 @@ class Pregel(PregelProtocol):
|
||||
values: dict[str, Any] | Any,
|
||||
as_node: Optional[str] = None,
|
||||
) -> RunnableConfig:
|
||||
checkpointer: Optional[BaseCheckpointSaver] = config[CONF].get(
|
||||
checkpointer: Optional[BaseCheckpointSaver] = ensure_config(config)[CONF].get(
|
||||
CONFIG_KEY_CHECKPOINTER, self.checkpointer
|
||||
)
|
||||
if not checkpointer:
|
||||
|
||||
Generated
+20
-23
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "aiohappyeyeballs"
|
||||
@@ -2862,30 +2862,30 @@ adal = ["adal (>=1.0.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "langchain"
|
||||
version = "0.3.1"
|
||||
version = "0.3.9"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "langchain-0.3.1-py3-none-any.whl", hash = "sha256:94e5ee7464d4366e4b158aa5704953c39701ea237b9ed4b200096d49e83bb3ae"},
|
||||
{file = "langchain-0.3.1.tar.gz", hash = "sha256:54d6e3abda2ec056875a231a418a4130ba7576e629e899067e499bfc847b7586"},
|
||||
{file = "langchain-0.3.9-py3-none-any.whl", hash = "sha256:ade5a1fee2f94f2e976a6c387f97d62cc7f0b9f26cfe0132a41d2bda761e1045"},
|
||||
{file = "langchain-0.3.9.tar.gz", hash = "sha256:4950c4ad627d0aa95ce6bda7de453e22059b7e7836b562a8f781fb0b05d7294c"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
aiohttp = ">=3.8.3,<4.0.0"
|
||||
async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""}
|
||||
langchain-core = ">=0.3.6,<0.4.0"
|
||||
langchain-core = ">=0.3.21,<0.4.0"
|
||||
langchain-text-splitters = ">=0.3.0,<0.4.0"
|
||||
langsmith = ">=0.1.17,<0.2.0"
|
||||
numpy = [
|
||||
{version = ">=1,<2", markers = "python_version < \"3.12\""},
|
||||
{version = ">=1.26.0,<2.0.0", markers = "python_version >= \"3.12\""},
|
||||
{version = ">=1.22.4,<2", markers = "python_version < \"3.12\""},
|
||||
{version = ">=1.26.2,<3", markers = "python_version >= \"3.12\""},
|
||||
]
|
||||
pydantic = ">=2.7.4,<3.0.0"
|
||||
PyYAML = ">=5.3"
|
||||
requests = ">=2,<3"
|
||||
SQLAlchemy = ">=1.4,<3"
|
||||
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
|
||||
tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-anthropic"
|
||||
@@ -2933,13 +2933,13 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<9.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "langchain-core"
|
||||
version = "0.3.15"
|
||||
version = "0.3.21"
|
||||
description = "Building applications with LLMs through composability"
|
||||
optional = false
|
||||
python-versions = "<4.0,>=3.9"
|
||||
files = [
|
||||
{file = "langchain_core-0.3.15-py3-none-any.whl", hash = "sha256:3d4ca6dbb8ed396a6ee061063832a2451b0ce8c345570f7b086ffa7288e4fa29"},
|
||||
{file = "langchain_core-0.3.15.tar.gz", hash = "sha256:b1a29787a4ffb7ec2103b4e97d435287201da7809b369740dd1e32f176325aba"},
|
||||
{file = "langchain_core-0.3.21-py3-none-any.whl", hash = "sha256:7e723dff80946a1198976c6876fea8326dc82566ef9bcb5f8d9188f738733665"},
|
||||
{file = "langchain_core-0.3.21.tar.gz", hash = "sha256:561b52b258ffa50a9fb11d7a1940ebfd915654d1ec95b35e81dfd5ee84143411"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -3035,7 +3035,7 @@ langchain-core = ">=0.3.0,<0.4.0"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph"
|
||||
version = "0.2.52"
|
||||
version = "0.2.54"
|
||||
description = "Building stateful, multi-actor applications with LLMs"
|
||||
optional = false
|
||||
python-versions = ">=3.9.0,<4.0"
|
||||
@@ -3045,7 +3045,7 @@ develop = true
|
||||
[package.dependencies]
|
||||
langchain-core = ">=0.2.43,<0.4.0,!=0.3.0,!=0.3.1,!=0.3.2,!=0.3.3,!=0.3.4,!=0.3.5,!=0.3.6,!=0.3.7,!=0.3.8,!=0.3.9,!=0.3.10,!=0.3.11,!=0.3.12,!=0.3.13,!=0.3.14"
|
||||
langgraph-checkpoint = "^2.0.4"
|
||||
langgraph-sdk = "^0.1.32"
|
||||
langgraph-sdk = "^0.1.42"
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
@@ -3053,7 +3053,7 @@ url = "libs/langgraph"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint"
|
||||
version = "2.0.5"
|
||||
version = "2.0.8"
|
||||
description = "Library with base interfaces for LangGraph checkpoint savers."
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -3070,7 +3070,7 @@ url = "libs/checkpoint"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-checkpoint-postgres"
|
||||
version = "2.0.3"
|
||||
version = "2.0.7"
|
||||
description = "Library with a Postgres implementation of LangGraph checkpoint saver."
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -3078,10 +3078,10 @@ files = []
|
||||
develop = true
|
||||
|
||||
[package.dependencies]
|
||||
langgraph-checkpoint = "^2.0.2"
|
||||
langgraph-checkpoint = "^2.0.7"
|
||||
orjson = ">=3.10.1"
|
||||
psycopg = "^3.0.0"
|
||||
psycopg-pool = "^3.0.0"
|
||||
psycopg = "^3.2.0"
|
||||
psycopg-pool = "^3.2.0"
|
||||
|
||||
[package.source]
|
||||
type = "directory"
|
||||
@@ -3106,7 +3106,7 @@ url = "libs/checkpoint-sqlite"
|
||||
|
||||
[[package]]
|
||||
name = "langgraph-sdk"
|
||||
version = "0.1.36"
|
||||
version = "0.1.42"
|
||||
description = "SDK for interacting with LangGraph API"
|
||||
optional = false
|
||||
python-versions = "^3.9.0,<4.0"
|
||||
@@ -3115,7 +3115,6 @@ develop = true
|
||||
|
||||
[package.dependencies]
|
||||
httpx = ">=0.25.2"
|
||||
httpx-sse = ">=0.4.0"
|
||||
orjson = ">=3.10.1"
|
||||
|
||||
[package.source]
|
||||
@@ -3586,7 +3585,6 @@ optional = false
|
||||
python-versions = ">=3.6"
|
||||
files = [
|
||||
{file = "mkdocs-redirects-1.2.1.tar.gz", hash = "sha256:9420066d70e2a6bb357adf86e67023dcdca1857f97f07c7fe450f8f1fb42f861"},
|
||||
{file = "mkdocs_redirects-1.2.1-py3-none-any.whl", hash = "sha256:497089f9e0219e7389304cffefccdfa1cac5ff9509f2cb706f4c9b221726dffb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -6964,7 +6962,6 @@ description = "Automatically mock your HTTP interactions to simplify and speed u
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "vcrpy-6.0.1-py2.py3-none-any.whl", hash = "sha256:621c3fb2d6bd8aa9f87532c688e4575bcbbde0c0afeb5ebdb7e14cac409edfdd"},
|
||||
{file = "vcrpy-6.0.1.tar.gz", hash = "sha256:9e023fee7f892baa0bbda2f7da7c8ac51165c1c6e38ff8688683a12a4bde9278"},
|
||||
]
|
||||
|
||||
@@ -7476,4 +7473,4 @@ type = ["pytest-mypy"]
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "776ee42630769f08e3896338f18ec81830166695d32d2208dc31dedb22d3b22d"
|
||||
content-hash = "cf18eed5e183fc4f7786d095540b6c9261e130750f2d1fcc427e08b78d522c61"
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ ruff = "^0.6.8"
|
||||
jupyter = "^1.1.1"
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
langchain = "^0.3.1"
|
||||
langchain = "^0.3.8"
|
||||
langchain-openai = "^0.2.0"
|
||||
langchain-anthropic = "^0.2.1"
|
||||
langchain-nomic = "^0.1.3"
|
||||
|
||||
Reference in New Issue
Block a user