LangChain Reference home pageLangChain ReferenceLangChain Reference
  • GitHub
  • Main Docs
Deep Agents
LangChain
LangGraph
Integrations
LangSmith
  • Overview
  • LangGraph Checkpoint
    LangGraph Store
    Checkpoint Postgres
    Store Postgres
    Checkpoint SQLite
    LangGraph Prebuilt
    LangGraph CLI
    LangGraph SDK
    LangGraph Supervisor
    LangGraph Swarm
    ⌘I

    LangChain Assistant

    Ask a question to get started

    Enter to send•Shift+Enter new line

    Menu

    LangGraph Checkpoint
    LangGraph Store
    Checkpoint Postgres
    Store Postgres
    Checkpoint SQLite
    LangGraph Prebuilt
    LangGraph CLI
    LangGraph SDK
    LangGraph Supervisor
    LangGraph Swarm
    Language
    Theme
    Pythonlanggraph-sdklanggraph_sdk
    Module●Since v0.1

    langgraph_sdk

    Functions

    function
    get_client
    function
    get_sync_client

    Classes

    class
    Auth
    class
    Encryption
    class
    EncryptionContext

    Modules

    module
    runtime
    module
    errors
    module
    client
    module
    sse
    module
    schema
    module
    cache
    module
    encryption
    module
    auth
    View source on GitHub

    Create and configure a LangGraphClient.

    The client provides programmatic access to LangSmith Deployment. It supports both remote servers and local in-process connections (when running inside a LangGraph server).

    Get a synchronous LangGraphClient instance.

    Context passed to encryption/decryption handlers.

    Contains arbitrary non-secret key-values that will be stored on encrypt. These key-values are intended to be sent to an external service that manages keys and handles the actual encryption and decryption of data.

    The LangGraph client implementations connect to the LangGraph API.

    This module provides both asynchronous (get_client(url="http://localhost:2024") or LangGraphClient) and synchronous (get_sync_client(url="http://localhost:2024") or SyncLanggraphClient) clients to interacting with the LangGraph API's core resources such as Assistants, Threads, Runs, and Cron jobs, as well as its persistent document Store.

    Adapted from httpx_sse to split lines on , , per the SSE spec.

    Data models for interacting with the LangGraph API.

    Key/value cache for use inside LangGraph deployments.

    Thin wrapper around langgraph_api.cache. Values must be JSON-serializable (dicts, lists, strings, numbers, booleans, None).

    Custom encryption support for LangGraph.

    .. warning:: This API is in beta and may change in future versions.

    This module provides a framework for implementing custom at-rest encryption in LangGraph applications. Similar to the Auth system, it allows developers to define custom encryption and decryption handlers that are executed server-side.

    Add custom authentication and authorization management to your LangGraph application.

    The Auth class provides a unified system for handling authentication and authorization in LangGraph applications. It supports custom user authentication protocols and fine-grained authorization rules for different resources and actions.

    To use, create a separate python file and add the path to the file to your LangGraph API configuration file (langgraph.json). Within that file, create an instance of the Auth class and register authentication and authorization handlers as needed.

    Example langgraph.json file:

    {
      "dependencies": ["."],
      "graphs": {
        "agent": "./my_agent/agent.py:graph"
      },
      "env": ".env",
      "auth": {
        "path": "./auth.py:my_auth"
      }

    Then the LangGraph server will load your auth file and run it server-side whenever a request comes in.

    Basic Usage
    from langgraph_sdk import Auth
    
    my_auth = Auth()
    
    @my_auth.authenticate
    async def authenticate(authorization: str) -> Auth.types.MinimalUserDict:
        user = await verify_token(authorization)  # Your token verification logic
        if not user:
            raise Auth.exceptions.HTTPException(
                status_code=401, detail="Unauthorized"
            )
        return {
            "identity": user["id"],
            "permissions": user.get("permissions", []),
        }
    
    # Default deny: reject all requests that don't have a specific handler
    @my_auth.on
    async def deny_all(ctx: Auth.types.AuthContext, value: Any) -> False:
        return False
    
    # Allow users to create threads with their own identity as owner
    @my_auth.on.threads.create
    async def allow_thread_create(
        ctx: Auth.types.AuthContext, value: Auth.types.on.threads.create.value
    ):
        metadata = value.setdefault("metadata", {})
        metadata["owner"] = ctx.user.identity
    
    # Allow users to read and search their own threads
    @my_auth.on.threads.read
    async def allow_thread_read(
        ctx: Auth.types.AuthContext, value: Auth.types.on.threads.read.value
    ) -> Auth.types.FilterType:
        return {"owner": ctx.user.identity}
    
    @my_auth.on.threads.search
    async def allow_thread_search(
        ctx: Auth.types.AuthContext, value: Auth.types.on.threads.search.value
    ) -> Auth.types.FilterType:
        return {"owner": ctx.user.identity}
    
    # Scope all store operations to the user's namespace
    @my_auth.on.store
    async def scope_store(ctx: Auth.types.AuthContext, value: Auth.types.on.store.value):
        namespace = tuple(value["namespace"]) if value.get("namespace") else ()
        if not namespace or namespace[0] != ctx.user.identity:
            namespace = (ctx.user.identity, *namespace)
        value["namespace"] = namespace
    Request Processing Flow
    1. Authentication (your @auth.authenticate handler) is performed first on every request
    2. For authorization, the most specific matching handler is called:
      • If a handler exists for the exact resource and action, it is used (e.g., @auth.on.threads.create)
      • Otherwise, if a handler exists for the resource with any action, it is used (e.g., @auth.on.threads)
      • Finally, if no specific handlers match, the global handler is used (e.g., @auth.on)
      • If no global handler is set, the request is accepted

    This allows you to set default behavior with a global handler while overriding specific routes as needed.

    Add custom at-rest encryption to your LangGraph application.

    .. warning:: This API is in beta and may change in future versions.

    The Encryption class provides a system for implementing custom encryption of data at rest in LangGraph applications. It supports encryption of both opaque blobs (like checkpoints) and structured JSON data (like metadata, context, kwargs, values, etc.).

    To use, create a separate Python file and add the path to the file to your LangGraph API configuration file (langgraph.json). Within that file, create an instance of the Encryption class and register encryption and decryption handlers as needed.

    Example langgraph.json file:

    {
      "dependencies": ["."],
      "graphs": {
        "agent": "./my_agent/agent.py:graph"
      },
      "env": ".env",
      "encryption": {
        "path": "./encryption.py:my_encryption"
      }
    }

    Then the LangGraph server will load your encryption file and use it to encrypt/decrypt data at rest.

    JSON Encryptors Must Preserve Keys

    JSON encryptors must not add or remove keys from the input dict. Only values may be transformed. This constraint is enforced at runtime by the server and exists because SQL JSONB merge operations (used for partial updates) work at the key level.

    Correct (per-key encryption):

    # Input:  {"secret": "value", "plain": "x"}
    # Output: {"secret": "<encrypted>", "plain": "x"}  ✓ Keys preserved

    Incorrect (key consolidation):

    # Input:  {"secret": "value", "plain": "x"}
    # Output: {"__encrypted__": "<blob>", "plain": "x"}  ✗ Key changed

    If your encryptor needs to store auxiliary data (DEK, IV, etc.), embed it within the encrypted value itself, not as separate keys.

    Basic Usage
    from langgraph_sdk import Encryption, EncryptionContext
    
    my_encryption = Encryption()
    
    SKIP_FIELDS = {"tenant_id", "owner", "thread_id", "assistant_id"}
    ENCRYPTED_PREFIX = "encrypted:"
    
    @my_encryption.encrypt.blob
    async def encrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes:
        return your_encrypt_bytes(blob)
    
    @my_encryption.decrypt.blob
    async def decrypt_blob(ctx: EncryptionContext, blob: bytes) -> bytes:
        return your_decrypt_bytes(blob)
    
    @my_encryption.encrypt.json
    async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
        result = {}
        for k, v in data.items():
            if k in SKIP_FIELDS or v is None:
                result[k] = v
            else:
                result[k] = ENCRYPTED_PREFIX + your_encrypt_string(v)
        return result
    
    @my_encryption.decrypt.json
    async def decrypt_json(ctx: EncryptionContext, data: dict) -> dict:
        result = {}
        for k, v in data.items():
            if isinstance(v, str) and v.startswith(ENCRYPTED_PREFIX):
                result[k] = your_decrypt_string(v[len(ENCRYPTED_PREFIX):])
            else:
                result[k] = v
        return result
    Field-Specific Logic

    The ctx.model and ctx.field attributes tell you which model type and specific field is being encrypted, allowing different logic:

    @my_encryption.encrypt.json
    async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
        if ctx.field == "metadata":
            # Metadata - standard encryption
            return encrypt_standard(data)
        elif ctx.field == "values":
            # Thread values - more sensitive, use stronger encryption
            return encrypt_sensitive(data)
        else:
            return encrypt_standard(data)
    Model/Field May Differ Between Encrypt and Decrypt

    Data encrypted with one (model, field) pair is not guaranteed to be decrypted with the same pair. The server performs SQL JSONB merges that can move encrypted values between models (e.g., cron metadata → run metadata). Your decryption logic must handle data regardless of the ctx.model or ctx.field values at decrypt time.

    Safe: Use ctx.model/ctx.field for logging or metrics only.

    Safe: Encrypt different keys based on ctx.field, but use a single decrypt handler that decrypts any value with the encrypted prefix (and passes through plaintext unchanged):

    ENCRYPTED_PREFIX = "enc:"
    
    @my_encryption.encrypt.json
    async def encrypt_json(ctx: EncryptionContext, data: dict) -> dict:
        # Encrypt different keys depending on the field
        if ctx.field == "context":
            keys_to_encrypt = {"api_key", "secret_token"}
        else:
            keys_to_encrypt = {"email", "ssn"}
        return {
            k: ENCRYPTED_PREFIX + encrypt(v) if k in keys_to_encrypt else v
            for k, v in data.items()
        }
    
    @my_encryption.decrypt.json
    async def decrypt_json(ctx: EncryptionContext, data: dict) -> dict:
        # Decrypt ANY value with the prefix, regardless of model/field
        return {
            k: decrypt(v[len(ENCRYPTED_PREFIX):])
               if isinstance(v, str) and v.startswith(ENCRYPTED_PREFIX)
               else v
            for k, v in data.items()
        }

    Unsafe: Using different encryption keys or algorithms based on ctx.model/ctx.field will cause decryption failures.