From fb79c64ed1fbaa27cd808cbdd2e0fe3d51bd440c Mon Sep 17 00:00:00 2001 From: Naomi Pentrel <5212232+npentrel@users.noreply.github.com> Date: Wed, 18 Mar 2026 16:28:59 +0100 Subject: [PATCH] feat: improve long-term memory docs and discoverability (#2957) --- .github/workflows/test-code-samples.yml | 37 ++ pyproject.toml | 2 + scripts/filter_broken_links_by_file.py | 33 ++ scripts/test_code_samples.py | 4 + src/code-samples/conftest.py | 128 ++++++ .../long-term-memory-create-agent-inmemory.py | 21 + .../long-term-memory-create-agent-inmemory.ts | 23 + .../long-term-memory-create-agent-postgres.py | 30 ++ .../long-term-memory-create-agent-postgres.ts | 24 ++ .../long-term-memory-read-tool-inmemory.py | 65 +++ .../long-term-memory-read-tool-inmemory.ts | 85 ++++ .../long-term-memory-read-tool-postgres.py | 57 +++ .../long-term-memory-read-tool-postgres.ts | 93 ++++ .../long-term-memory-storage-inmemory.py | 50 +++ .../long-term-memory-storage-inmemory.ts | 66 +++ .../long-term-memory-storage-postgres.py | 58 +++ .../long-term-memory-storage-postgres.ts | 90 ++++ .../long-term-memory-write-tool-inmemory.py | 67 +++ .../long-term-memory-write-tool-inmemory.ts | 88 ++++ .../long-term-memory-write-tool-postgres.py | 69 +++ .../long-term-memory-write-tool-postgres.ts | 114 +++++ src/code-samples/package-lock.json | 210 ++++++++- src/code-samples/package.json | 2 + src/oss/langchain/long-term-memory.mdx | 404 ++++++------------ src/oss/langchain/short-term-memory.mdx | 4 + ...g-term-memory-create-agent-postgres-js.mdx | 2 + ...g-term-memory-create-agent-postgres-py.mdx | 2 +- ...long-term-memory-read-tool-postgres-js.mdx | 1 + ...long-term-memory-read-tool-postgres-py.mdx | 2 +- .../long-term-memory-storage-inmemory-py.mdx | 2 +- .../long-term-memory-storage-postgres-js.mdx | 2 + .../long-term-memory-storage-postgres-py.mdx | 20 +- ...ong-term-memory-write-tool-postgres-js.mdx | 2 + ...ong-term-memory-write-tool-postgres-py.mdx | 2 +- .../streaming-reasoning-tokens-py.mdx | 21 +- 35 files changed, 1577 insertions(+), 303 deletions(-) create mode 100644 scripts/filter_broken_links_by_file.py create mode 100644 src/code-samples/conftest.py create mode 100644 src/code-samples/langchain/long-term-memory-create-agent-inmemory.py create mode 100644 src/code-samples/langchain/long-term-memory-create-agent-inmemory.ts create mode 100644 src/code-samples/langchain/long-term-memory-create-agent-postgres.py create mode 100644 src/code-samples/langchain/long-term-memory-create-agent-postgres.ts create mode 100644 src/code-samples/langchain/long-term-memory-read-tool-inmemory.py create mode 100644 src/code-samples/langchain/long-term-memory-read-tool-inmemory.ts create mode 100644 src/code-samples/langchain/long-term-memory-read-tool-postgres.py create mode 100644 src/code-samples/langchain/long-term-memory-read-tool-postgres.ts create mode 100644 src/code-samples/langchain/long-term-memory-storage-inmemory.py create mode 100644 src/code-samples/langchain/long-term-memory-storage-inmemory.ts create mode 100644 src/code-samples/langchain/long-term-memory-storage-postgres.py create mode 100644 src/code-samples/langchain/long-term-memory-storage-postgres.ts create mode 100644 src/code-samples/langchain/long-term-memory-write-tool-inmemory.py create mode 100644 src/code-samples/langchain/long-term-memory-write-tool-inmemory.ts create mode 100644 src/code-samples/langchain/long-term-memory-write-tool-postgres.py create mode 100644 src/code-samples/langchain/long-term-memory-write-tool-postgres.ts diff --git a/.github/workflows/test-code-samples.yml b/.github/workflows/test-code-samples.yml index 5cd2b1014..25a3877be 100644 --- a/.github/workflows/test-code-samples.yml +++ b/.github/workflows/test-code-samples.yml @@ -8,6 +8,7 @@ on: pull_request: paths: - "src/code-samples/**" + - ".github/workflows/test-code-samples.yml" schedule: # Run every Sunday at 00:00 UTC - cron: "0 0 * * 0" @@ -20,6 +21,19 @@ jobs: test-code-samples: runs-on: ubuntu-latest timeout-minutes: 15 + services: + postgres: + image: pgvector/pgvector:pg17 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 steps: - name: Checkout uses: actions/checkout@v6 @@ -67,10 +81,33 @@ jobs: with: node-version: "20" + - name: Wait for PostgreSQL + run: | + for i in $(seq 1 30); do + if python3 -c " + import socket + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(2) + try: + s.connect(('127.0.0.1', 5432)) + s.close() + except OSError: + exit(1) + "; then + echo "PostgreSQL is ready" + exit 0 + fi + echo "Waiting for PostgreSQL... ($i/30)" + sleep 2 + done + echo "PostgreSQL did not become ready" + exit 1 + - name: Test code samples id: test env: ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + POSTGRES_URI: postgresql://postgres:postgres@127.0.0.1:5432/postgres?sslmode=disable run: | if [[ "${{ steps.files.outputs.run_all }}" == "true" ]]; then echo "Running all code samples..." diff --git a/pyproject.toml b/pyproject.toml index 3cdf90020..2fd3a1a8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,8 @@ dependencies = [ "nbconvert>=7.16.6", "langchain>=1.0.1", "langchain-anthropic>=1.0.0", + "langgraph-checkpoint-postgres", + "psycopg[binary,pool]>=3.2.0", ] diff --git a/scripts/filter_broken_links_by_file.py b/scripts/filter_broken_links_by_file.py new file mode 100644 index 000000000..df43551a7 --- /dev/null +++ b/scripts/filter_broken_links_by_file.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +"""Filter mint broken-links output to only include blocks for specified files. + +Reads from stdin, writes to stdout. +Usage: mint broken-links 2>&1 | python filter_broken_links_by_file.py [pattern1 pattern2 ...] +Patterns match if the build path (e.g. langsmith/admin.mdx) contains the pattern. +""" + +import sys + + +def main() -> None: + patterns = sys.argv[1:] + lines = sys.stdin.read().split("\n") + out: list[str] = [] + current_file: str | None = None + + for line in lines: + if not line or line[0].isspace(): + # Indented line or blank: belongs to current block + if patterns and current_file and any(p in current_file for p in patterns): + out.append(line) + else: + # File header + current_file = line.strip() + if not patterns or any(p in current_file for p in patterns): + out.append(line) + + sys.stdout.write("\n".join(out)) + + +if __name__ == "__main__": + main() diff --git a/scripts/test_code_samples.py b/scripts/test_code_samples.py index a563b6030..4384865ee 100644 --- a/scripts/test_code_samples.py +++ b/scripts/test_code_samples.py @@ -99,6 +99,8 @@ def main() -> int: success = False try: + # Pass full env so POSTGRES_URI, ANTHROPIC_API_KEY etc. reach child processes + env = os.environ.copy() if lang == "python": result = subprocess.run( ["uv", "run", "python", str(file_path)], @@ -107,6 +109,7 @@ def main() -> int: capture_output=True, text=True, timeout=TIMEOUT_SECONDS, + env=env, ) success = result.returncode == 0 stdout = result.stdout or "" @@ -120,6 +123,7 @@ def main() -> int: capture_output=True, text=True, timeout=TIMEOUT_SECONDS, + env=env, ) success = result.returncode == 0 stdout = result.stdout or "" diff --git a/src/code-samples/conftest.py b/src/code-samples/conftest.py new file mode 100644 index 000000000..e0acbed04 --- /dev/null +++ b/src/code-samples/conftest.py @@ -0,0 +1,128 @@ +"""PostgreSQL setup for code samples. + +This module provides PostgreSQL connection setup for code samples. +It attempts to use testcontainers if available, otherwise provides +utilities to work with environment-configured postgres. +""" + +import os +import subprocess +import sys +import time + +_DEFAULT_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" + + +def get_postgres_uri() -> str: + """Get PostgreSQL connection URI. + + Tries multiple approaches in order: + 1. Check POSTGRES_URI environment variable + 2. Attempt to use testcontainers to spin up postgres + 3. Try docker directly + 4. Fall back to default local postgres connection + """ + # Check environment variable first + if env_uri := os.environ.get("POSTGRES_URI"): + return env_uri + + # Try testcontainers + try: + from testcontainers.postgres import ( # type: ignore[import-not-found] + PostgresContainer, + ) + + # Store container in a global so it persists + if not hasattr(get_postgres_uri, "_container"): + # Use pgvector image which includes the vector extension + container = PostgresContainer("pgvector/pgvector:pg17") + container.start() + get_postgres_uri._container = container # type: ignore[attr-defined] + # Give it a moment to fully start + time.sleep(2) + + return get_postgres_uri._container.get_connection_url() # type: ignore[attr-defined] + except ImportError: + print("conftest: testcontainers not installed, trying docker", file=sys.stderr) + + # Try to use docker directly if testcontainers not available + try: + # Check if postgres container is already running + result = subprocess.run( + [ + "docker", + "ps", + "--filter", + "name=langchain-docs-postgres", + "--format", + "{{.Names}}", + ], + capture_output=True, + text=True, + check=True, + timeout=5, + ) + + if "langchain-docs-postgres" not in result.stdout: + # Start a postgres container with pgvector extension + subprocess.run( + [ + "docker", + "run", + "-d", + "--name", + "langchain-docs-postgres", + "-e", + "POSTGRES_PASSWORD=postgres", + "-e", + "POSTGRES_DB=postgres", + "-p", + "5442:5432", + "pgvector/pgvector:pg17", + ], + check=True, + capture_output=True, + timeout=30, + ) + # Give it time to start + time.sleep(3) + + return _DEFAULT_URI + except FileNotFoundError: + print("conftest: docker not found, using default URI", file=sys.stderr) + except subprocess.TimeoutExpired as e: + print( + f"conftest: docker timed out after {e.timeout}s, using default URI", + file=sys.stderr, + ) + except subprocess.CalledProcessError as e: + print( + f"conftest: docker failed (exit {e.returncode}), using default URI", + file=sys.stderr, + ) + + # Fall back to default (assumes postgres is running locally) + print(f"conftest: falling back to default URI: {_DEFAULT_URI}", file=sys.stderr) + return _DEFAULT_URI + + +def prepare_postgres_store(uri: str) -> None: + """Drop existing store tables so setup() creates a fresh schema. + + Use before PostgresStore.from_conn_string when tests share a database + (e.g. CI) and may see leftover tables from a different schema version. + """ + import psycopg + + try: + with psycopg.connect(uri, autocommit=True) as conn: + with conn.cursor() as cur: + cur.execute( + "DROP TABLE IF EXISTS public.store_vectors CASCADE; " + "DROP TABLE IF EXISTS public.store CASCADE; " + "DROP TABLE IF EXISTS public.store_migrations CASCADE;" + ) + except psycopg.OperationalError as e: + print(f"conftest: could not connect to clean tables: {e}", file=sys.stderr) + except psycopg.Error as e: + print(f"conftest: failed to drop store tables: {e}", file=sys.stderr) diff --git a/src/code-samples/langchain/long-term-memory-create-agent-inmemory.py b/src/code-samples/langchain/long-term-memory-create-agent-inmemory.py new file mode 100644 index 000000000..83e6089db --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-create-agent-inmemory.py @@ -0,0 +1,21 @@ +# :snippet-start: long-term-memory-create-agent-inmemory-py +from langchain.agents import create_agent +from langchain_core.runnables import Runnable +from langgraph.store.memory import InMemoryStore + +# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. +store = InMemoryStore() + +agent: Runnable = create_agent( + "claude-sonnet-4-6", + tools=[], + store=store, +) +# :snippet-end: + +# :remove-start: +if __name__ == "__main__": + # Verify the agent was created successfully + assert agent is not None + print("✓ Agent with InMemoryStore created successfully") +# :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-create-agent-inmemory.ts b/src/code-samples/langchain/long-term-memory-create-agent-inmemory.ts new file mode 100644 index 000000000..ad178020d --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-create-agent-inmemory.ts @@ -0,0 +1,23 @@ +// :snippet-start: long-term-memory-create-agent-inmemory-js +import { createAgent } from "langchain"; +import { InMemoryStore } from "@langchain/langgraph"; + +// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. +const store = new InMemoryStore(); + +const agent = createAgent({ + model: "claude-sonnet-4-6", + tools: [], + store, +}); +// :snippet-end: + +// :remove-start: +if (import.meta.url === `file://${process.argv[1]}`) { + // Verify the agent was created successfully + if (!agent) { + throw new Error("Agent creation failed"); + } + console.log("✓ Agent with InMemoryStore created successfully"); +} +// :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-create-agent-postgres.py b/src/code-samples/langchain/long-term-memory-create-agent-postgres.py new file mode 100644 index 000000000..183239c49 --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-create-agent-postgres.py @@ -0,0 +1,30 @@ +# :snippet-start: long-term-memory-create-agent-postgres-py +from langchain.agents import create_agent +from langchain_core.runnables import Runnable +from langgraph.store.postgres import PostgresStore # type: ignore[import-not-found] + +DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" +# :remove-start: +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) +from conftest import get_postgres_uri, prepare_postgres_store + +DB_URI = get_postgres_uri() +prepare_postgres_store(DB_URI) +# :remove-end: + +with PostgresStore.from_conn_string(DB_URI) as store: + store.setup() + agent: Runnable = create_agent( + "claude-sonnet-4-6", + tools=[], + store=store, + ) +# :snippet-end: + +# :remove-start: +assert agent is not None +print("✓ Agent with PostgresStore created successfully") +# :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-create-agent-postgres.ts b/src/code-samples/langchain/long-term-memory-create-agent-postgres.ts new file mode 100644 index 000000000..87d72502e --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-create-agent-postgres.ts @@ -0,0 +1,24 @@ +// :snippet-start: long-term-memory-create-agent-postgres-js +import { createAgent } from "langchain"; +import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres/store"; + +const DB_URI = + process.env.POSTGRES_URI ?? + "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; +const store = PostgresStore.fromConnString(DB_URI); +await store.setup(); + +const agent = createAgent({ + model: "claude-sonnet-4-6", + tools: [], + store, +}); +// :snippet-end: + +// :remove-start: +if (!agent) { + throw new Error("Agent creation failed"); +} +console.log("✓ Agent with PostgresStore created successfully"); +await store.stop(); +// :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-read-tool-inmemory.py b/src/code-samples/langchain/long-term-memory-read-tool-inmemory.py new file mode 100644 index 000000000..3f9797090 --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-read-tool-inmemory.py @@ -0,0 +1,65 @@ +# :snippet-start: long-term-memory-read-tool-inmemory-py +from dataclasses import dataclass + +from langchain.agents import create_agent +from langchain.tools import ToolRuntime, tool +from langchain_core.runnables import Runnable +from langgraph.store.memory import InMemoryStore + + +@dataclass +class Context: + user_id: str + + +# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production. +store = InMemoryStore() + +# Write sample data to the store using the put method +store.put( + ( + "users", + ), # Namespace to group related data together (users namespace for user data) + "user_123", # Key within the namespace (user ID as key) + { + "name": "John Smith", + "language": "English", + }, # Data to store for the given user +) + + +@tool +def get_user_info(runtime: ToolRuntime[Context]) -> str: + """Look up user info.""" + # Access the store - same as that provided to `create_agent` + assert runtime.store is not None + user_id = runtime.context.user_id + # Retrieve data from store - returns StoreValue object with value and metadata + user_info = runtime.store.get(("users",), user_id) + return str(user_info.value) if user_info else "Unknown user" + + +agent: Runnable = create_agent( + model="claude-sonnet-4-6", + tools=[get_user_info], + # Pass store to agent - enables agent to access store when running tools + store=store, + context_schema=Context, +) + +# Run the agent +agent.invoke( + {"messages": [{"role": "user", "content": "look up user information"}]}, + context=Context(user_id="user_123"), +) +# :snippet-end: + +# :remove-start: +if __name__ == "__main__": + # Verify the store has the data + result = store.get(("users",), "user_123") + assert result is not None + assert result.value["name"] == "John Smith" + + print("✓ Read tool with InMemoryStore works correctly") +# :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-read-tool-inmemory.ts b/src/code-samples/langchain/long-term-memory-read-tool-inmemory.ts new file mode 100644 index 000000000..ff206bd1b --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-read-tool-inmemory.ts @@ -0,0 +1,85 @@ +// :snippet-start: long-term-memory-read-tool-inmemory-js +import * as z from "zod"; +import { createAgent, tool, type ToolRuntime } from "langchain"; +import { InMemoryStore } from "@langchain/langgraph"; + +// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production. +const store = new InMemoryStore(); +const contextSchema = z.object({ + userId: z.string(), +}); + +// Write sample data to the store using the put method +await store.put( + ["users"], // Namespace to group related data together (users namespace for user data) + "user_123", // Key within the namespace (user ID as key) + { + name: "John Smith", + language: "English", + }, // Data to store for the given user +); + +const getUserInfo = tool( + // Look up user info. + async (_, runtime: ToolRuntime>) => { + // Access the store - same as that provided to `createAgent` + const userId = runtime.context.userId; + if (!userId) { + throw new Error("userId is required"); + } + // Retrieve data from store - returns StoreValue object with value and metadata + const userInfo = await runtime.store.get(["users"], userId); + return userInfo?.value ? JSON.stringify(userInfo.value) : "Unknown user"; + }, + { + name: "getUserInfo", + description: "Look up user info by userId from the store.", + schema: z.object({}), + }, +); + +const agent = createAgent({ + model: "claude-sonnet-4-6", + tools: [getUserInfo], + contextSchema, + // Pass store to agent - enables agent to access store when running tools + store, +}); + +// Run the agent +const result = await agent.invoke( + { messages: [{ role: "user", content: "look up user information" }] }, + { context: { userId: "user_123" } }, +); + +console.log(result.messages.at(-1)?.content); + +/** + * Outputs: + * User Information: + * - **Name:** John Smith + * - **Language:** English + */ +// :snippet-end: + +// :remove-start: +async function main() { + // Verify the store has the data + const storedData = await store.get(["users"], "user_123"); + if (!storedData) { + throw new Error("Expected data to be in store"); + } + if (storedData.value["name"] !== "John Smith") { + throw new Error('Expected name to be "John Smith"'); + } + + console.log("✓ Read tool with InMemoryStore works correctly"); +} + +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); +} +// :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-read-tool-postgres.py b/src/code-samples/langchain/long-term-memory-read-tool-postgres.py new file mode 100644 index 000000000..2cd98bb57 --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-read-tool-postgres.py @@ -0,0 +1,57 @@ +# :snippet-start: long-term-memory-read-tool-postgres-py +from dataclasses import dataclass + +from langchain.agents import create_agent +from langchain.tools import ToolRuntime, tool +from langchain_core.runnables import Runnable +from langgraph.store.postgres import PostgresStore # type: ignore[import-not-found] + + +@dataclass +class Context: + user_id: str + + +DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" +# :remove-start: +import os +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) +from conftest import get_postgres_uri, prepare_postgres_store + +DB_URI = get_postgres_uri() +prepare_postgres_store(DB_URI) +os.environ.setdefault("ANTHROPIC_API_KEY", "sk-ant-test-key") +# :remove-end: + +with PostgresStore.from_conn_string(DB_URI) as store: + store.setup() + store.put(("users",), "user_123", {"name": "John Smith", "language": "English"}) + + @tool + def get_user_info(runtime: ToolRuntime[Context]) -> str: + """Look up user info.""" + assert runtime.store is not None + user_info = runtime.store.get(("users",), runtime.context.user_id) + return str(user_info.value) if user_info else "Unknown user" + + agent: Runnable = create_agent( + "claude-sonnet-4-6", + tools=[get_user_info], + store=store, + context_schema=Context, + ) + + result = agent.invoke( + {"messages": [{"role": "user", "content": "look up user information"}]}, + context=Context(user_id="user_123"), + ) +# :snippet-end: + +# :remove-start: +assert result is not None +assert "messages" in result +print("✓ Read tool with PostgresStore works correctly") +# :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-read-tool-postgres.ts b/src/code-samples/langchain/long-term-memory-read-tool-postgres.ts new file mode 100644 index 000000000..3fff0c801 --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-read-tool-postgres.ts @@ -0,0 +1,93 @@ +// :snippet-start: long-term-memory-read-tool-postgres-js +import * as z from "zod"; +import { createAgent, tool, type ToolRuntime } from "langchain"; +import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres/store"; + +const DB_URI = + process.env.POSTGRES_URI ?? + "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; +const store = PostgresStore.fromConnString(DB_URI); +// :remove-start: +// Drop old store tables if they exist (prior version used different schema). +// setup() uses CREATE TABLE IF NOT EXISTS and does not migrate existing tables. +await ( + store as { core: { pool: { query: (q: string) => Promise } } } +).core.pool.query( + "DROP TABLE IF EXISTS public.store_vectors CASCADE; DROP TABLE IF EXISTS public.store CASCADE; DROP TABLE IF EXISTS public.store_migrations CASCADE;", +); +// :remove-end: +await store.setup(); + +const contextSchema = z.object({ userId: z.string() }); + +await store.put(["users"], "user_123", { + name: "John Smith", + language: "English", +}); + +const getUserInfo = tool( + async (_, runtime: ToolRuntime>) => { + const userId = runtime.context.userId; + if (!userId) throw new Error("userId is required"); + const userInfo = await runtime.store.get(["users"], userId); + return userInfo?.value ? JSON.stringify(userInfo.value) : "Unknown user"; + }, + { + name: "getUserInfo", + description: "Look up user info by userId from the store.", + schema: z.object({}), + }, +); + +const agent = createAgent({ + model: "claude-sonnet-4-6", + tools: [getUserInfo], + contextSchema, + store, +}); + +await agent.invoke( + { messages: [{ role: "user", content: "look up user information" }] }, + { context: { userId: "user_123" } }, +); +// :snippet-end: + +// :remove-start: +async function main() { + const DB_URI = + process.env.POSTGRES_URI || + "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; + const store = PostgresStore.fromConnString(DB_URI); + + try { + await store.setup(); + + const contextSchema = z.object({ userId: z.string() }); + + await store.put(["users"], "user_123", { + name: "John Smith", + language: "English", + }); + + // Verify the store has the data + const storedData = await store.get(["users"], "user_123"); + if (!storedData) { + throw new Error("Expected data to be in store"); + } + if (storedData.value["name"] !== "John Smith") { + throw new Error('Expected name to be "John Smith"'); + } + + console.log("✓ Read tool with PostgresStore works correctly"); + } finally { + await store.stop(); + } +} + +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); +} +// :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-storage-inmemory.py b/src/code-samples/langchain/long-term-memory-storage-inmemory.py new file mode 100644 index 000000000..6d3d4ee7a --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-storage-inmemory.py @@ -0,0 +1,50 @@ +# :snippet-start: long-term-memory-storage-inmemory-py +from collections.abc import Sequence + +from langgraph.store.base import IndexConfig +from langgraph.store.memory import InMemoryStore + + +def embed(texts: Sequence[str]) -> list[list[float]]: + # Replace with an actual embedding function or LangChain embeddings object + return [[1.0, 2.0] for _ in texts] + + +# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. +store = InMemoryStore(index=IndexConfig(embed=embed, dims=2)) +user_id = "my-user" +application_context = "chitchat" +namespace = (user_id, application_context) +store.put( + namespace, + "a-memory", + { + "rules": [ + "User likes short, direct language", + "User only speaks English & python", + ], + "my-key": "my-value", + }, +) +# get the "memory" by ID +item = store.get(namespace, "a-memory") +# search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity +items = store.search( + namespace, filter={"my-key": "my-value"}, query="language preferences" +) +# :snippet-end: + +# :remove-start: +if __name__ == "__main__": + # Verify the operations work + assert item is not None + assert item.value["my-key"] == "my-value" + assert "rules" in item.value + assert len(item.value["rules"]) == 2 + + # Verify search returns results + assert len(items) > 0 + assert items[0].value["my-key"] == "my-value" + + print("✓ InMemoryStore operations work correctly") +# :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-storage-inmemory.ts b/src/code-samples/langchain/long-term-memory-storage-inmemory.ts new file mode 100644 index 000000000..2c36d5376 --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-storage-inmemory.ts @@ -0,0 +1,66 @@ +// :snippet-start: long-term-memory-storage-inmemory-js +import { InMemoryStore } from "@langchain/langgraph"; + +const embed = (texts: string[]): number[][] => { + // Replace with an actual embedding function or LangChain embeddings object + return texts.map(() => [1.0, 2.0]); +}; + +// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. +const store = new InMemoryStore({ index: { embed, dims: 2 } }); +const userId = "my-user"; +const applicationContext = "chitchat"; +const namespace = [userId, applicationContext]; + +await store.put(namespace, "a-memory", { + rules: [ + "User likes short, direct language", + "User only speaks English & TypeScript", + ], + "my-key": "my-value", +}); + +// get the "memory" by ID +const item = await store.get(namespace, "a-memory"); + +// search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity +const items = await store.search(namespace, { + filter: { "my-key": "my-value" }, + query: "language preferences", +}); +// :snippet-end: + +// :remove-start: +async function main() { + // Verify the operations work + if (!item) { + throw new Error("Item should not be null"); + } + if (item.value["my-key"] !== "my-value") { + throw new Error('Expected my-key to be "my-value"'); + } + if (!item.value["rules"]) { + throw new Error("Expected rules to exist"); + } + if ((item.value["rules"] as string[]).length !== 2) { + throw new Error("Expected 2 rules"); + } + + // Verify search returns results + if (items.length === 0) { + throw new Error("Expected search to return results"); + } + if (items[0].value["my-key"] !== "my-value") { + throw new Error('Expected search result my-key to be "my-value"'); + } + + console.log("✓ InMemoryStore operations work correctly"); +} + +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); +} +// :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-storage-postgres.py b/src/code-samples/langchain/long-term-memory-storage-postgres.py new file mode 100644 index 000000000..011f593ec --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-storage-postgres.py @@ -0,0 +1,58 @@ +# :snippet-start: long-term-memory-storage-postgres-py +from collections.abc import Sequence + +from langgraph.store.base import IndexConfig +from langgraph.store.postgres import PostgresStore # type: ignore[import-not-found] + + +def embed(texts: Sequence[str]) -> list[list[float]]: + # Replace with an actual embedding function or LangChain embeddings object + return [[1.0, 2.0] for _ in texts] + + +DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" +# :remove-start: +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) +from conftest import get_postgres_uri, prepare_postgres_store + +DB_URI = get_postgres_uri() +prepare_postgres_store(DB_URI) +# :remove-end: + +with PostgresStore.from_conn_string( + DB_URI, + index=IndexConfig(embed=embed, dims=2), # type: ignore[arg-type] +) as store: + store.setup() + user_id = "my-user" + application_context = "chitchat" + namespace = (user_id, application_context) + store.put( + namespace, + "a-memory", + { + "rules": [ + "User likes short, direct language", + "User only speaks English & python", + ], + "my-key": "my-value", + }, + ) + item = store.get(namespace, "a-memory") + items = store.search( + namespace, filter={"my-key": "my-value"}, query="language preferences" + ) +# :snippet-end: + +# :remove-start: +if __name__ == "__main__": + assert item is not None + assert item.value["my-key"] == "my-value" + assert "rules" in item.value + assert len(item.value["rules"]) == 2 + assert len(items) > 0 + print("✓ PostgresStore operations work correctly") +# :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-storage-postgres.ts b/src/code-samples/langchain/long-term-memory-storage-postgres.ts new file mode 100644 index 000000000..7fe2edf86 --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-storage-postgres.ts @@ -0,0 +1,90 @@ +// :snippet-start: long-term-memory-storage-postgres-js +import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres/store"; + +const embed = (texts: string[]): number[][] => { + return texts.map(() => [1.0, 2.0]); +}; + +const DB_URI = + process.env.POSTGRES_URI ?? + "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; +const store = PostgresStore.fromConnString(DB_URI, { + index: { embed, dims: 2 }, +}); +// :remove-start: +// Drop tables from prior runs (Python samples use different schema; CI shares one DB) +await ( + store as { core: { pool: { query: (q: string) => Promise } } } +).core.pool.query( + "DROP TABLE IF EXISTS public.store_vectors CASCADE; DROP TABLE IF EXISTS public.store CASCADE; DROP TABLE IF EXISTS public.store_migrations CASCADE;", +); +// :remove-end: +await store.setup(); + +const userId = "my-user"; +const applicationContext = "chitchat"; +const namespace = [userId, applicationContext]; + +await store.put(namespace, "a-memory", { + rules: [ + "User likes short, direct language", + "User only speaks English & TypeScript", + ], + "my-key": "my-value", +}); + +const item = await store.get(namespace, "a-memory"); +const items = await store.search(namespace, { + filter: { "my-key": "my-value" }, + query: "language preferences", +}); +// :snippet-end: + +// :remove-start: + +try { + await store.setup(); + + const userId = "my-user"; + const applicationContext = "chitchat"; + const namespace = [userId, applicationContext]; + + await store.put(namespace, "a-memory", { + rules: [ + "User likes short, direct language", + "User only speaks English & TypeScript", + ], + "my-key": "my-value", + }); + + const item = await store.get(namespace, "a-memory"); + const items = await store.search(namespace, { + filter: { "my-key": "my-value" }, + query: "language preferences", + }); + + // Verify the operations work + if (!item) { + throw new Error("Item should not be null"); + } + if (item.value["my-key"] !== "my-value") { + throw new Error('Expected my-key to be "my-value"'); + } + if (!item.value["rules"]) { + throw new Error("Expected rules to exist"); + } + if ((item.value["rules"] as string[]).length !== 2) { + throw new Error("Expected 2 rules"); + } + + // Verify search returns results + if (items.length === 0) { + throw new Error("Expected search to return results"); + } + + console.log("✓ PostgresStore operations work correctly"); +} finally { + await store.stop(); +} + +// :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-write-tool-inmemory.py b/src/code-samples/langchain/long-term-memory-write-tool-inmemory.py new file mode 100644 index 000000000..f77a1884c --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-write-tool-inmemory.py @@ -0,0 +1,67 @@ +# :snippet-start: long-term-memory-write-tool-inmemory-py +from dataclasses import dataclass +from typing import Any, cast + +from langchain.agents import create_agent +from langchain.tools import ToolRuntime, tool +from langchain_core.runnables import Runnable +from langgraph.store.memory import InMemoryStore +from typing_extensions import TypedDict + +# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production. +store = InMemoryStore() + + +@dataclass +class Context: + user_id: str + + +# TypedDict defines the structure of user information for the LLM +class UserInfo(TypedDict): + name: str + + +# Tool that allows agent to update user information (useful for chat applications) +@tool +def save_user_info(user_info: UserInfo, runtime: ToolRuntime[Context]) -> str: + """Save user info.""" + # Access the store - same as that provided to `create_agent` + assert runtime.store is not None + store = runtime.store + user_id = runtime.context.user_id + # Store data in the store (namespace, key, data) + store.put(("users",), user_id, cast("dict[str, Any]", user_info)) + return "Successfully saved user info." + + +agent: Runnable = create_agent( + model="claude-sonnet-4-6", + tools=[save_user_info], + store=store, + context_schema=Context, +) + +# Run the agent +agent.invoke( + {"messages": [{"role": "user", "content": "My name is John Smith"}]}, + # user_id passed in context to identify whose information is being updated + context=Context(user_id="user_123"), +) + +# You can access the store directly to get the value +item = store.get(("users",), "user_123") +# :snippet-end: + +# :remove-start: +if __name__ == "__main__": + # Test by putting data directly into the store + store.put(("users",), "user_123", {"name": "John Smith"}) + + # Verify data was saved + saved_data = store.get(("users",), "user_123") + assert saved_data is not None + assert saved_data.value["name"] == "John Smith" + + print("✓ Write tool with InMemoryStore works correctly") +# :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-write-tool-inmemory.ts b/src/code-samples/langchain/long-term-memory-write-tool-inmemory.ts new file mode 100644 index 000000000..840b30fb1 --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-write-tool-inmemory.ts @@ -0,0 +1,88 @@ +// :snippet-start: long-term-memory-write-tool-inmemory-js +import * as z from "zod"; +import { tool, createAgent, type ToolRuntime } from "langchain"; +import { InMemoryStore } from "@langchain/langgraph"; + +// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production. +const store = new InMemoryStore(); + +const contextSchema = z.object({ + userId: z.string(), +}); + +// Schema defines the structure of user information for the LLM +const UserInfo = z.object({ + name: z.string(), +}); + +// Tool that allows agent to update user information (useful for chat applications) +const saveUserInfo = tool( + async ( + userInfo: z.infer, + runtime: ToolRuntime>, + ) => { + const userId = runtime.context.userId; + if (!userId) { + throw new Error("userId is required"); + } + // Store data in the store (namespace, key, data) + await runtime.store.put(["users"], userId, userInfo); + return "Successfully saved user info."; + }, + { + name: "save_user_info", + description: "Save user info", + schema: UserInfo, + }, +); + +const agent = createAgent({ + model: "claude-sonnet-4-6", + tools: [saveUserInfo], + contextSchema, + store, +}); + +// Run the agent +await agent.invoke( + { messages: [{ role: "user", content: "My name is John Smith" }] }, + // userId passed in context to identify whose information is being updated + { context: { userId: "user_123" } }, +); + +// You can access the store directly to get the value +const result = await store.get(["users"], "user_123"); +console.log(result?.value); // Output: { name: "John Smith" } +// :snippet-end: + +// :remove-start: +async function main() { + // Test the tool directly - pass context and store in config (same shape as ToolRuntime) + const saveResult = await saveUserInfo.invoke( + { name: "John Smith" }, + { context: { userId: "user_123" }, store }, + ); + + if (saveResult !== "Successfully saved user info.") { + throw new Error("Expected save to succeed"); + } + + // Verify data was saved + const savedData = await store.get(["users"], "user_123"); + if (!savedData) { + throw new Error("Expected data to be saved"); + } + if (savedData.value["name"] !== "John Smith") { + throw new Error('Expected name to be "John Smith"'); + } + + console.log("✓ Write tool with InMemoryStore works correctly"); +} + +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); +} +// :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-write-tool-postgres.py b/src/code-samples/langchain/long-term-memory-write-tool-postgres.py new file mode 100644 index 000000000..7ded3221f --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-write-tool-postgres.py @@ -0,0 +1,69 @@ +# :snippet-start: long-term-memory-write-tool-postgres-py +from dataclasses import dataclass +from typing import Any, cast + +from langchain.agents import create_agent +from langchain.tools import ToolRuntime, tool +from langchain_core.runnables import Runnable +from langgraph.store.postgres import PostgresStore # type: ignore[import-not-found] +from typing_extensions import TypedDict + + +@dataclass +class Context: + user_id: str + + +class UserInfo(TypedDict): + name: str + + +@tool +def save_user_info(user_info: UserInfo, runtime: ToolRuntime[Context]) -> str: + """Save user info.""" + assert runtime.store is not None + runtime.store.put( + ("users",), runtime.context.user_id, cast("dict[str, Any]", user_info) + ) + return "Successfully saved user info." + + +DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" +# :remove-start: +import os +import sys +from pathlib import Path + +os.environ.setdefault("ANTHROPIC_API_KEY", "sk-ant-test-key") +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) +from conftest import get_postgres_uri, prepare_postgres_store + +DB_URI = get_postgres_uri() +prepare_postgres_store(DB_URI) +# :remove-end: + +with PostgresStore.from_conn_string(DB_URI) as store: + store.setup() + agent: Runnable = create_agent( + "claude-sonnet-4-6", + tools=[save_user_info], + store=store, + context_schema=Context, + ) + + agent.invoke( + {"messages": [{"role": "user", "content": "My name is John Smith"}]}, + context=Context(user_id="user_123"), + ) +# :snippet-end: + +# :remove-start: +if __name__ == "__main__": + with PostgresStore.from_conn_string(DB_URI) as store: + store.setup() + store.put(("users",), "user_123", {"name": "John Smith"}) + saved_data = store.get(("users",), "user_123") + assert saved_data is not None + assert saved_data.value["name"] == "John Smith" + print("✓ Write tool with PostgresStore works correctly") +# :remove-end: diff --git a/src/code-samples/langchain/long-term-memory-write-tool-postgres.ts b/src/code-samples/langchain/long-term-memory-write-tool-postgres.ts new file mode 100644 index 000000000..73f7f99e3 --- /dev/null +++ b/src/code-samples/langchain/long-term-memory-write-tool-postgres.ts @@ -0,0 +1,114 @@ +// :snippet-start: long-term-memory-write-tool-postgres-js +import * as z from "zod"; +import { tool, createAgent, type ToolRuntime } from "langchain"; +import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres/store"; + +const DB_URI = + process.env.POSTGRES_URI ?? + "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; +const store = PostgresStore.fromConnString(DB_URI); +// :remove-start: +// Drop tables from prior runs (Python samples use different schema; CI shares one DB) +await ( + store as { core: { pool: { query: (q: string) => Promise } } } +).core.pool.query( + "DROP TABLE IF EXISTS public.store_vectors CASCADE; DROP TABLE IF EXISTS public.store CASCADE; DROP TABLE IF EXISTS public.store_migrations CASCADE;", +); +// :remove-end: +await store.setup(); + +const contextSchema = z.object({ userId: z.string() }); + +const UserInfo = z.object({ name: z.string() }); + +const saveUserInfo = tool( + async ( + userInfo: z.infer, + runtime: ToolRuntime>, + ) => { + const userId = runtime.context.userId; + if (!userId) throw new Error("userId is required"); + await runtime.store.put(["users"], userId, userInfo); + return "Successfully saved user info."; + }, + { name: "save_user_info", description: "Save user info", schema: UserInfo }, +); + +const agent = createAgent({ + model: "claude-sonnet-4-6", + tools: [saveUserInfo], + contextSchema, + store, +}); + +await agent.invoke( + { messages: [{ role: "user", content: "My name is John Smith" }] }, + { context: { userId: "user_123" } }, +); + +const result = await store.get(["users"], "user_123"); +console.log(result?.value); +// :snippet-end: + +// :remove-start: +async function main() { + const DB_URI = + process.env.POSTGRES_URI || + "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; + const store = PostgresStore.fromConnString(DB_URI); + + try { + await store.setup(); + + const contextSchema = z.object({ userId: z.string() }); + const UserInfo = z.object({ name: z.string() }); + + const saveUserInfo = tool( + async ( + userInfo: z.infer, + runtime: ToolRuntime>, + ) => { + const userId = runtime.context.userId; + if (!userId) throw new Error("userId is required"); + await runtime.store.put(["users"], userId, userInfo); + return "Successfully saved user info."; + }, + { + name: "save_user_info", + description: "Save user info", + schema: UserInfo, + }, + ); + + // Test the tool directly - pass context and store in config (same shape as ToolRuntime) + const saveResult = await saveUserInfo.invoke( + { name: "John Smith" }, + { context: { userId: "user_123" }, store }, + ); + + if (saveResult !== "Successfully saved user info.") { + throw new Error("Expected save to succeed"); + } + + // Verify data was saved + const savedData = await store.get(["users"], "user_123"); + if (!savedData) { + throw new Error("Expected data to be saved"); + } + if (savedData.value["name"] !== "John Smith") { + throw new Error('Expected name to be "John Smith"'); + } + + console.log("✓ Write tool with PostgresStore works correctly"); + } finally { + await store.stop(); + } +} + +if (import.meta.url === `file://${process.argv[1]}`) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); +} +// :remove-end: diff --git a/src/code-samples/package-lock.json b/src/code-samples/package-lock.json index 91b58ec4f..8190a19bd 100644 --- a/src/code-samples/package-lock.json +++ b/src/code-samples/package-lock.json @@ -10,6 +10,8 @@ "license": "ISC", "dependencies": { "@langchain/anthropic": "^1.3.22", + "@langchain/langgraph-checkpoint-postgres": "^1.0.1", + "@langchain/openai": "^1.2.12", "langchain": "^1.2.28", "zod": "^3.23.0" }, @@ -50,7 +52,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", @@ -573,6 +576,22 @@ "@langchain/core": "^1.0.1" } }, + "node_modules/@langchain/langgraph-checkpoint-postgres": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@langchain/langgraph-checkpoint-postgres/-/langgraph-checkpoint-postgres-1.0.1.tgz", + "integrity": "sha512-cRXOLYZc0egMjyAQfBblWXdoBS+WKwEbCEuE+/f88XHJ1bq5jVz7aycbpjF/7F5EykG7xmybQOz5eUdg3neRzg==", + "license": "MIT", + "dependencies": { + "pg": "^8.12.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": "^1.0.1", + "@langchain/langgraph-checkpoint": "^1.0.0" + } + }, "node_modules/@langchain/langgraph-checkpoint/node_modules/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -674,6 +693,23 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/@langchain/openai": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-1.2.12.tgz", + "integrity": "sha512-Im6PPNujrfkZk4vpc9JAjbeERg+RbNtWRe3KSFOP7aNGa/yZ+XD69lxXwbsZGaZkbiUN/hwe9RYeisUfThb5wg==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^6.24.0", + "zod": "^3.25.76 || ^4" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@langchain/core": "^1.1.30" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -697,6 +733,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -729,6 +766,7 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -762,6 +800,7 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -947,10 +986,32 @@ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "license": "MIT", + "peer": true, "bin": { "mustache": "bin/mustache" } }, + "node_modules/openai": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.27.0.tgz", + "integrity": "sha512-osTKySlrdYrLYTt0zjhY8yp0JUBmWDCN+Q+QxsV4xMQnnoVFpylgKGgxwN8sSdTNw0G4y+WUXs4eCMWpyDNWZQ==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -1003,6 +1064,134 @@ "node": ">=8" } }, + "node_modules/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.12.0", + "pg-pool": "^3.13.0", + "pg-protocol": "^1.13.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", + "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", + "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", + "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -1031,6 +1220,15 @@ "integrity": "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw==", "license": "MIT" }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", @@ -1070,12 +1268,20 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/code-samples/package.json b/src/code-samples/package.json index 69f838102..39f6633fb 100644 --- a/src/code-samples/package.json +++ b/src/code-samples/package.json @@ -11,6 +11,8 @@ "license": "ISC", "dependencies": { "@langchain/anthropic": "^1.3.22", + "@langchain/langgraph-checkpoint-postgres": "^1.0.1", + "@langchain/openai": "^1.2.12", "langchain": "^1.2.28", "zod": "^3.23.0" }, diff --git a/src/oss/langchain/long-term-memory.mdx b/src/oss/langchain/long-term-memory.mdx index 35d528273..f9c37ce42 100644 --- a/src/oss/langchain/long-term-memory.mdx +++ b/src/oss/langchain/long-term-memory.mdx @@ -1,11 +1,74 @@ --- title: Long-term memory +description: Add long-term memory to LangChain agents to store and recall data across conversations and sessions --- -## Overview +import LongTermMemoryCreateAgentInmemoryPy from '/snippets/code-samples/long-term-memory-create-agent-inmemory-py.mdx'; +import LongTermMemoryCreateAgentInmemoryJs from '/snippets/code-samples/long-term-memory-create-agent-inmemory-js.mdx'; +import LongTermMemoryCreateAgentPostgresPy from '/snippets/code-samples/long-term-memory-create-agent-postgres-py.mdx'; +import LongTermMemoryCreateAgentPostgresJs from '/snippets/code-samples/long-term-memory-create-agent-postgres-js.mdx'; +import LongTermMemoryStorageInmemoryPy from '/snippets/code-samples/long-term-memory-storage-inmemory-py.mdx'; +import LongTermMemoryStorageInmemoryJs from '/snippets/code-samples/long-term-memory-storage-inmemory-js.mdx'; +import LongTermMemoryStoragePostgresPy from '/snippets/code-samples/long-term-memory-storage-postgres-py.mdx'; +import LongTermMemoryStoragePostgresJs from '/snippets/code-samples/long-term-memory-storage-postgres-js.mdx'; +import LongTermMemoryReadToolInmemoryPy from '/snippets/code-samples/long-term-memory-read-tool-inmemory-py.mdx'; +import LongTermMemoryReadToolInmemoryJs from '/snippets/code-samples/long-term-memory-read-tool-inmemory-js.mdx'; +import LongTermMemoryReadToolPostgresPy from '/snippets/code-samples/long-term-memory-read-tool-postgres-py.mdx'; +import LongTermMemoryReadToolPostgresJs from '/snippets/code-samples/long-term-memory-read-tool-postgres-js.mdx'; +import LongTermMemoryWriteToolInmemoryPy from '/snippets/code-samples/long-term-memory-write-tool-inmemory-py.mdx'; +import LongTermMemoryWriteToolInmemoryJs from '/snippets/code-samples/long-term-memory-write-tool-inmemory-js.mdx'; +import LongTermMemoryWriteToolPostgresPy from '/snippets/code-samples/long-term-memory-write-tool-postgres-py.mdx'; +import LongTermMemoryWriteToolPostgresJs from '/snippets/code-samples/long-term-memory-write-tool-postgres-js.mdx'; -LangChain agents use [LangGraph persistence](/oss/langgraph/persistence#memory-store) to enable long-term memory. This is a more advanced topic and requires knowledge of LangGraph to use. +Long-term memory lets your agent store and recall information across different conversations and sessions. +Unlike [short-term memory](/oss/langchain/short-term-memory), which is scoped to a single thread, long-term memory persists across threads and can be recalled at any time. +Long-term memory is built on [LangGraph stores](/oss/langgraph/persistence#memory-store), which save data as JSON documents organized by namespace and key. + +## Usage + +To add long-term memory to an agent, create a store and pass it to @[`create_agent`]: + + + +:::python + + + +::: + +:::js + + + +::: + + +:::python +```shell +pip install langgraph-checkpoint-postgres +``` + + + +::: + +:::js +```shell +npm install @langchain/langgraph-checkpoint-postgres +``` + + + +::: + + + +Tools can then read from and write to the store using the `runtime.store` parameter. See [Read long-term memory in tools](#read-long-term-memory-in-tools) and [Write long-term memory from tools](#write-long-term-memory-from-tools) for examples. + + + For a deeper dive into memory types (semantic, episodic, procedural) and strategies for writing memories, see the [Memory conceptual guide](/oss/concepts/memory#long-term-memory). + ## Memory storage @@ -15,319 +78,96 @@ Each memory is organized under a custom `namespace` (similar to a folder) and a This structure enables hierarchical organization of memories. Cross-namespace searching is then supported through content filters. + + :::python -```python -from langgraph.store.memory import InMemoryStore + -def embed(texts: list[str]) -> list[list[float]]: - # Replace with an actual embedding function or LangChain embeddings object - return [[1.0, 2.0] * len(texts)] - - -# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. -store = InMemoryStore(index={"embed": embed, "dims": 2}) # [!code highlight] -user_id = "my-user" -application_context = "chitchat" -namespace = (user_id, application_context) # [!code highlight] -store.put( # [!code highlight] - namespace, - "a-memory", - { - "rules": [ - "User likes short, direct language", - "User only speaks English & python", - ], - "my-key": "my-value", - }, -) -# get the "memory" by ID -item = store.get(namespace, "a-memory") # [!code highlight] -# search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity -items = store.search( # [!code highlight] - namespace, filter={"my-key": "my-value"}, query="language preferences" -) -``` ::: :::js -```typescript -import { InMemoryStore } from "@langchain/langgraph"; -const embed = (texts: string[]): number[][] => { - // Replace with an actual embedding function or LangChain embeddings object - return texts.map(() => [1.0, 2.0]); -}; + -// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. -const store = new InMemoryStore({ index: { embed, dims: 2 } }); // [!code highlight] -const userId = "my-user"; -const applicationContext = "chitchat"; -const namespace = [userId, applicationContext]; // [!code highlight] - -await store.put( // [!code highlight] - namespace, - "a-memory", - { - rules: [ - "User likes short, direct language", - "User only speaks English & TypeScript", - ], - "my-key": "my-value", - } -); - -// get the "memory" by ID -const item = await store.get(namespace, "a-memory"); // [!code highlight] - -// search for "memories" within this namespace, filtering on content equivalence, sorted by vector similarity -const items = await store.search( // [!code highlight] - namespace, - { - filter: { "my-key": "my-value" }, - query: "language preferences" - } -); -``` ::: + + +:::python + + + +::: + +:::js + + + +::: + + For more information about the memory store, see the [Persistence](/oss/langgraph/persistence#memory-store) guide. ## Read long-term memory in tools + + :::python -```python A tool the agent can use to look up user information -from dataclasses import dataclass -from langchain_core.runnables import RunnableConfig -from langchain.agents import create_agent -from langchain.tools import tool, ToolRuntime -from langgraph.store.memory import InMemoryStore - - -@dataclass -class Context: - user_id: str - -# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production. -store = InMemoryStore() # [!code highlight] - -# Write sample data to the store using the put method -store.put( # [!code highlight] - ("users",), # Namespace to group related data together (users namespace for user data) - "user_123", # Key within the namespace (user ID as key) - { - "name": "John Smith", - "language": "English", - } # Data to store for the given user -) - -@tool -def get_user_info(runtime: ToolRuntime[Context]) -> str: - """Look up user info.""" - # Access the store - same as that provided to `create_agent` - store = runtime.store # [!code highlight] - user_id = runtime.context.user_id - # Retrieve data from store - returns StoreValue object with value and metadata - user_info = store.get(("users",), user_id) # [!code highlight] - return str(user_info.value) if user_info else "Unknown user" - -agent = create_agent( - model="claude-sonnet-4-6", - tools=[get_user_info], - # Pass store to agent - enables agent to access store when running tools - store=store, # [!code highlight] - context_schema=Context -) - -# Run the agent -agent.invoke( - {"messages": [{"role": "user", "content": "look up user information"}]}, - context=Context(user_id="user_123") # [!code highlight] -) -``` + ::: :::js -```typescript A tool the agent can use to look up user information -import * as z from "zod"; -import { createAgent, tool, type ToolRuntime } from "langchain"; -import { InMemoryStore } from "@langchain/langgraph"; -// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production. -const store = new InMemoryStore(); // [!code highlight] -const contextSchema = z.object({ - userId: z.string(), -}); - -// Write sample data to the store using the put method -await store.put( // [!code highlight] - ["users"], // Namespace to group related data together (users namespace for user data) - "user_123", // Key within the namespace (user ID as key) - { - name: "John Smith", - language: "English", - } // Data to store for the given user -); - -const getUserInfo = tool( - // Look up user info. - async (_, runtime: ToolRuntime>) => { - // Access the store - same as that provided to `createAgent` - const userId = runtime.context.userId; - if (!userId) { - throw new Error("userId is required"); - } - // Retrieve data from store - returns StoreValue object with value and metadata - const userInfo = await runtime.store.get(["users"], userId); - return userInfo?.value ? JSON.stringify(userInfo.value) : "Unknown user"; - }, - { - name: "getUserInfo", - description: "Look up user info by userId from the store.", - schema: z.object({}), - } -); - -const agent = createAgent({ - model: "gpt-4.1-mini", - tools: [getUserInfo], - contextSchema, - // Pass store to agent - enables agent to access store when running tools - store, // [!code highlight] -}); - -// Run the agent -const result = await agent.invoke( - { messages: [{ role: "user", content: "look up user information" }] }, - { context: { userId: "user_123" } } // [!code highlight] -); - -console.log(result.messages.at(-1)?.content); - -/** - * Outputs: - * User Information: - * - Name: John Smith - * - Language: English - */ -``` + ::: + + +:::python + + + +::: + +:::js + + + +::: + + ## Write long-term memory from tools + + :::python -```python Example of a tool that updates user information -from dataclasses import dataclass -from typing_extensions import TypedDict -from langchain.agents import create_agent -from langchain.tools import tool, ToolRuntime -from langgraph.store.memory import InMemoryStore - - -# InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production. -store = InMemoryStore() # [!code highlight] - -@dataclass -class Context: - user_id: str - -# TypedDict defines the structure of user information for the LLM -class UserInfo(TypedDict): - name: str - -# Tool that allows agent to update user information (useful for chat applications) -@tool -def save_user_info(user_info: UserInfo, runtime: ToolRuntime[Context]) -> str: - """Save user info.""" - # Access the store - same as that provided to `create_agent` - store = runtime.store # [!code highlight] - user_id = runtime.context.user_id # [!code highlight] - # Store data in the store (namespace, key, data) - store.put(("users",), user_id, user_info) # [!code highlight] - return "Successfully saved user info." - -agent = create_agent( - model="claude-sonnet-4-6", - tools=[save_user_info], - store=store, # [!code highlight] - context_schema=Context -) - -# Run the agent -agent.invoke( - {"messages": [{"role": "user", "content": "My name is John Smith"}]}, - # user_id passed in context to identify whose information is being updated - context=Context(user_id="user_123") # [!code highlight] -) - -# You can access the store directly to get the value -store.get(("users",), "user_123").value -``` + ::: :::js -```typescript Example of a tool that updates user information -import * as z from "zod"; -import { tool, createAgent, type ToolRuntime } from "langchain"; -import { InMemoryStore } from "@langchain/langgraph"; -// InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production. -const store = new InMemoryStore(); // [!code highlight] - -const contextSchema = z.object({ - userId: z.string(), -}); - -// Schema defines the structure of user information for the LLM -const UserInfo = z.object({ - name: z.string(), -}); - -// Tool that allows agent to update user information (useful for chat applications) -const saveUserInfo = tool( - async ( - userInfo: z.infer, - runtime: ToolRuntime> - ) => { - const userId = runtime.context.userId; - if (!userId) { - throw new Error("userId is required"); - } - // Store data in the store (namespace, key, data) - await runtime.store.put(["users"], userId, userInfo); - return "Successfully saved user info."; - }, - { - name: "save_user_info", - description: "Save user info", - schema: UserInfo, - } -); - -const agent = createAgent({ - model: "gpt-4.1-mini", - tools: [saveUserInfo], - contextSchema, - store, // [!code highlight] -}); - -// Run the agent -await agent.invoke( - { messages: [{ role: "user", content: "My name is John Smith" }] }, - // userId passed in context to identify whose information is being updated - { context: { userId: "user_123" } } // [!code highlight] -); - -// You can access the store directly to get the value -const result = await store.get(["users"], "user_123"); -console.log(result?.value); // Output: { name: "John Smith" } - -``` + ::: + + +:::python + + + +::: + +:::js + + + +::: + + diff --git a/src/oss/langchain/short-term-memory.mdx b/src/oss/langchain/short-term-memory.mdx index ac561291a..ff8e10cc3 100644 --- a/src/oss/langchain/short-term-memory.mdx +++ b/src/oss/langchain/short-term-memory.mdx @@ -18,6 +18,10 @@ Even if your model supports the full context length, most LLMs still perform poo Chat models accept context using [messages](/oss/langchain/messages), which include instructions (a system message) and inputs (human messages). In chat applications, messages alternate between human inputs and model responses, resulting in a list of messages that grows longer over time. Because context windows are limited, many applications can benefit from using techniques to remove or "forget" stale information. + + Need to remember information **across** conversations? Use [long-term memory](/oss/langchain/long-term-memory) to store and recall user-specific or application-level data across different threads and sessions. + + ## Usage To add short-term memory (thread-level persistence) to an agent, you need to specify a `checkpointer` when creating an agent. diff --git a/src/snippets/code-samples/long-term-memory-create-agent-postgres-js.mdx b/src/snippets/code-samples/long-term-memory-create-agent-postgres-js.mdx index 08f72cdfe..9d4e5d907 100644 --- a/src/snippets/code-samples/long-term-memory-create-agent-postgres-js.mdx +++ b/src/snippets/code-samples/long-term-memory-create-agent-postgres-js.mdx @@ -3,8 +3,10 @@ import { createAgent } from "langchain"; import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres/store"; const DB_URI = + process.env.POSTGRES_URI ?? "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; const store = PostgresStore.fromConnString(DB_URI); +await store.setup(); const agent = createAgent({ model: "claude-sonnet-4-6", diff --git a/src/snippets/code-samples/long-term-memory-create-agent-postgres-py.mdx b/src/snippets/code-samples/long-term-memory-create-agent-postgres-py.mdx index 911baf198..577f02c99 100644 --- a/src/snippets/code-samples/long-term-memory-create-agent-postgres-py.mdx +++ b/src/snippets/code-samples/long-term-memory-create-agent-postgres-py.mdx @@ -1,7 +1,7 @@ ```python from langchain.agents import create_agent from langchain_core.runnables import Runnable -from langgraph.store.postgres import PostgresStore +from langgraph.store.postgres import PostgresStore # type: ignore[import-not-found] DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" diff --git a/src/snippets/code-samples/long-term-memory-read-tool-postgres-js.mdx b/src/snippets/code-samples/long-term-memory-read-tool-postgres-js.mdx index 19b40a69c..85cbd8cb4 100644 --- a/src/snippets/code-samples/long-term-memory-read-tool-postgres-js.mdx +++ b/src/snippets/code-samples/long-term-memory-read-tool-postgres-js.mdx @@ -4,6 +4,7 @@ import { createAgent, tool, type ToolRuntime } from "langchain"; import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres/store"; const DB_URI = + process.env.POSTGRES_URI ?? "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; const store = PostgresStore.fromConnString(DB_URI); await store.setup(); diff --git a/src/snippets/code-samples/long-term-memory-read-tool-postgres-py.mdx b/src/snippets/code-samples/long-term-memory-read-tool-postgres-py.mdx index 6ec974d8c..b6b41015f 100644 --- a/src/snippets/code-samples/long-term-memory-read-tool-postgres-py.mdx +++ b/src/snippets/code-samples/long-term-memory-read-tool-postgres-py.mdx @@ -4,7 +4,7 @@ from dataclasses import dataclass from langchain.agents import create_agent from langchain.tools import ToolRuntime, tool from langchain_core.runnables import Runnable -from langgraph.store.postgres import PostgresStore +from langgraph.store.postgres import PostgresStore # type: ignore[import-not-found] @dataclass diff --git a/src/snippets/code-samples/long-term-memory-storage-inmemory-py.mdx b/src/snippets/code-samples/long-term-memory-storage-inmemory-py.mdx index 2be7a47d1..660824701 100644 --- a/src/snippets/code-samples/long-term-memory-storage-inmemory-py.mdx +++ b/src/snippets/code-samples/long-term-memory-storage-inmemory-py.mdx @@ -7,7 +7,7 @@ from langgraph.store.memory import InMemoryStore def embed(texts: Sequence[str]) -> list[list[float]]: # Replace with an actual embedding function or LangChain embeddings object - return [[1.0, 2.0] * len(texts)] + return [[1.0, 2.0] for _ in texts] # InMemoryStore saves data to an in-memory dictionary. Use a DB-backed store in production use. diff --git a/src/snippets/code-samples/long-term-memory-storage-postgres-js.mdx b/src/snippets/code-samples/long-term-memory-storage-postgres-js.mdx index 76b38e2d8..60a15363f 100644 --- a/src/snippets/code-samples/long-term-memory-storage-postgres-js.mdx +++ b/src/snippets/code-samples/long-term-memory-storage-postgres-js.mdx @@ -6,10 +6,12 @@ const embed = (texts: string[]): number[][] => { }; const DB_URI = + process.env.POSTGRES_URI ?? "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; const store = PostgresStore.fromConnString(DB_URI, { index: { embed, dims: 2 }, }); +await store.setup(); const userId = "my-user"; const applicationContext = "chitchat"; diff --git a/src/snippets/code-samples/long-term-memory-storage-postgres-py.mdx b/src/snippets/code-samples/long-term-memory-storage-postgres-py.mdx index f656d700a..e948aa65b 100644 --- a/src/snippets/code-samples/long-term-memory-storage-postgres-py.mdx +++ b/src/snippets/code-samples/long-term-memory-storage-postgres-py.mdx @@ -1,9 +1,21 @@ ```python -from langgraph.store.postgres import PostgresStore +from collections.abc import Sequence + +from langgraph.store.base import IndexConfig +from langgraph.store.postgres import PostgresStore # type: ignore[import-not-found] + + +def embed(texts: Sequence[str]) -> list[list[float]]: + # Replace with an actual embedding function or LangChain embeddings object + return [[1.0, 2.0] for _ in texts] + DB_URI = "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable" -with PostgresStore.from_conn_string(DB_URI) as store: +with PostgresStore.from_conn_string( + DB_URI, + index=IndexConfig(embed=embed, dims=2), # type: ignore[arg-type] +) as store: store.setup() user_id = "my-user" application_context = "chitchat" @@ -20,5 +32,7 @@ with PostgresStore.from_conn_string(DB_URI) as store: }, ) item = store.get(namespace, "a-memory") - items = store.search(namespace, filter={"my-key": "my-value"}) + items = store.search( + namespace, filter={"my-key": "my-value"}, query="language preferences" + ) ``` diff --git a/src/snippets/code-samples/long-term-memory-write-tool-postgres-js.mdx b/src/snippets/code-samples/long-term-memory-write-tool-postgres-js.mdx index 1dc3acf67..1fe37b838 100644 --- a/src/snippets/code-samples/long-term-memory-write-tool-postgres-js.mdx +++ b/src/snippets/code-samples/long-term-memory-write-tool-postgres-js.mdx @@ -4,8 +4,10 @@ import { tool, createAgent, type ToolRuntime } from "langchain"; import { PostgresStore } from "@langchain/langgraph-checkpoint-postgres/store"; const DB_URI = + process.env.POSTGRES_URI ?? "postgresql://postgres:postgres@localhost:5442/postgres?sslmode=disable"; const store = PostgresStore.fromConnString(DB_URI); +await store.setup(); const contextSchema = z.object({ userId: z.string() }); diff --git a/src/snippets/code-samples/long-term-memory-write-tool-postgres-py.mdx b/src/snippets/code-samples/long-term-memory-write-tool-postgres-py.mdx index 24bfea860..0e08e9840 100644 --- a/src/snippets/code-samples/long-term-memory-write-tool-postgres-py.mdx +++ b/src/snippets/code-samples/long-term-memory-write-tool-postgres-py.mdx @@ -5,7 +5,7 @@ from typing import Any, cast from langchain.agents import create_agent from langchain.tools import ToolRuntime, tool from langchain_core.runnables import Runnable -from langgraph.store.postgres import PostgresStore +from langgraph.store.postgres import PostgresStore # type: ignore[import-not-found] from typing_extensions import TypedDict diff --git a/src/snippets/code-samples/streaming-reasoning-tokens-py.mdx b/src/snippets/code-samples/streaming-reasoning-tokens-py.mdx index e6abcb3c1..635b75fad 100644 --- a/src/snippets/code-samples/streaming-reasoning-tokens-py.mdx +++ b/src/snippets/code-samples/streaming-reasoning-tokens-py.mdx @@ -21,19 +21,16 @@ agent: Runnable = create_agent( tools=[get_weather], ) -for chunk in agent.stream( +for token, metadata in agent.stream( {"messages": [{"role": "user", "content": "What is the weather in SF?"}]}, stream_mode="messages", # [!code highlight] - version="v2", # [!code highlight] ): - if chunk["type"] == "messages": # [!code highlight] - token, metadata = chunk["data"] # [!code highlight] - if not isinstance(token, AIMessageChunk): - continue - reasoning = [b for b in token.content_blocks if b["type"] == "reasoning"] - text = [b for b in token.content_blocks if b["type"] == "text"] - if reasoning: - print(f"[thinking] {reasoning[0]['reasoning']}", end="") - if text: - print(text[0]["text"], end="") + if not isinstance(token, AIMessageChunk): + continue + reasoning = [b for b in token.content_blocks if b["type"] == "reasoning"] + text = [b for b in token.content_blocks if b["type"] == "text"] + if reasoning: + print(f"[thinking] {reasoning[0]['reasoning']}", end="") + if text: + print(text[0]["text"], end="") ```