跪拜 Guibai
← Back to the summary

LangChain ToolRuntime: A three-way split for context, state, and store

1. What is ToolRuntime

ToolRuntime is a runtime object that LangChain automatically passes in when executing tools.

Tool parameters fall into two categories:


from langchain.tools import ToolRuntime, tool

@tool
def search(keyword: str, runtime: ToolRuntime) -> str:
    # keyword is filled by the model.
    # runtime is passed in automatically by LangChain.
    return f"Search: {keyword}"

2. Three core objects

Object Purpose Typical contents
context Fixed conditions for this run user ID, permissions, database connections
state Temporary state of the current flow messages, steps, temporary results
store Data saved across requests user preferences, long-term memory

Memory rule: context manages run conditions, state manages the current process, store manages long-term data.

3. context: passing in user and run dependencies

context is created by the program and then passed to the Agent. When user identity is needed, use information already verified by the login system; do not read the user ID from the user's text.

from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.tools import ToolRuntime, tool


@dataclass
class RequestContext:
    # These values are passed in by the program; the model cannot modify them.
    user_id: int
    role: str


@tool
def show_user(runtime: ToolRuntime[RequestContext]) -> str:
    # The tool reads user identity from context, and does not accept a user_id passed by the model.
    return f"User {runtime.context.user_id}, role {runtime.context.role}"


# Configure the model according to the actual environment.
model = init_chat_model("openai:gpt-4o-mini")

# context_schema only declares the context type.
agent = create_agent(
    model=model,
    tools=[show_user],
    context_schema=RequestContext,
)

# Real user information should come from the login system.
context = RequestContext(user_id=1001, role="user")

# The context parameter is what actually passes user information to the Agent.
result = agent.invoke(
    {"messages": [{"role": "user", "content": "Query current user"}]},
    context=context,
)
print(result["messages"][-1].content)

Use cases: permission checks, querying data by user, passing database sessions, passing tenant information.

Note: context_schema does not automatically create user information; you must first create RequestContext(...), then pass it in using context=....

4. state: temporary state of the current flow

state is what the Agent is currently processing. The simplest example is reading the current message count.

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.tools import ToolRuntime, tool


@tool
def count_messages(runtime: ToolRuntime) -> str:
    # messages is the message list built into Agent state.
    # The second argument to get, [], is the default: returns an empty list when there is no messages.
    messages = runtime.state.get("messages", [])
    return f"There are currently {len(messages)} messages"


model = init_chat_model("openai:gpt-4o-mini")
agent = create_agent(
    model=model,
    tools=[count_messages],
)

# When the model calls the tool, ToolRuntime automatically carries the current state.
result = agent.invoke(
    {"messages": [{"role": "user", "content": "Count the current messages"}]}
)
print(result["messages"][-1].content)

Use cases: reading messages, recording current steps, saving retry counts and temporary query results.

Real usage: counting model invocation times

The current code puts model_call_count into state, and a middleware increments it by 1 after each model call:

from typing import Any, NotRequired

from langchain.agents import AgentState
from langchain.agents.middleware import AgentMiddleware
from langgraph.runtime import Runtime


class CustomAgentState(AgentState):
    # AgentState already provides messages; here we only add a call counter.
    model_call_count: NotRequired[int]


class ModelCallLimitMiddleware(AgentMiddleware[CustomAgentState]):
    # Register the extended state type.
    state_schema = CustomAgentState

    def after_model(
        self, state: CustomAgentState, runtime: Runtime
    ) -> dict[str, Any]:
        # Start at 0 if not recorded, increment by 1 after each model call.
        count = state.get("model_call_count", 0)
        return {"model_call_count": count + 1}

When streaming, the updates event can read the state updated by the middleware:

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model

model = init_chat_model("openai:gpt-4o-mini")
agent = create_agent(
    model=model,
    middleware=[ModelCallLimitMiddleware()],
)

async def read_model_call_count() -> None:
    input_state = {
        "messages": [{"role": "user", "content": "Hello"}],
        "model_call_count": 0,
    }
    model_call_count = 0

    async for mode, data in agent.astream(
        input_state,
        stream_mode=["messages", "updates"],
    ):
        if mode != "updates":
            continue

        update = data.get("ModelCallLimitMiddleware.after_model")
        if update:
            model_call_count = update["model_call_count"]

    print(f"Model call count this round: {model_call_count}")

This count belongs only to the current Agent flow, suitable for logging, rate limiting, and debugging information.

5. store: saving data across requests

store is suitable for saving data that is still needed in the next request, for example:

It is not appropriate to unconditionally put all chat content into store. Before long-term saving, first judge whether the content has value, and isolate namespaces by user or tenant.

In real projects, store.put() and store.get() are usually wrapped into tools. The tool gets the current logged-in user ID from context, so external callers don't need to pass the user ID again:

from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.tools import ToolRuntime, tool
from langgraph.store.memory import InMemoryStore


@dataclass
class UserContext:
    # This ID should be passed in by the login system, not filled by the model.
    user_id: str


@tool(description="Save the user's favorite color.")
def save_color(color: str, runtime: ToolRuntime[UserContext]) -> str:
    # The tool saves data through runtime.store.
    namespace = ("user", runtime.context.user_id)
    runtime.store.put(
        namespace,
        "favorite_color",
        {"value": color},
    )
    return "Color preference saved"

@tool(description="Read the user's previously saved favorite color.")
def read_color(runtime: ToolRuntime[UserContext]) -> str:
    # When reading, use the same namespace and key.
    namespace = ("user", runtime.context.user_id)
    item = runtime.store.get(namespace, "favorite_color")
    if item is None:
        return "No color preference saved yet"
    return f"Favorite color is: {item.value['value']}"


# The store should be created when the application starts so multiple requests can share it.
store = InMemoryStore()
model = init_chat_model("openai:gpt-4o-mini")

# Pass the store and context type to the Agent so tools can use runtime.store and runtime.context.
agent = create_agent(
    model=model,
    tools=[save_color, read_color],
    context_schema=UserContext,
    store=store,
)

# Pass the current logged-in user's context on each request.
context = UserContext(user_id="1001")
agent.invoke(
    {"messages": [{"role": "user", "content": "Remember that I like blue"}]},
    context=context,
)

# The next request still uses the same store, so the tool can read the previously saved content.
next_context = UserContext(user_id="1001")
result = agent.invoke(
    {"messages": [{"role": "user", "content": "What color do I like?"}]},
    context=next_context,
)
print(result["messages"][-1].content)

InMemoryStore only lives in memory; it is lost after a program restart. For permanent storage, switch to a persistence implementation such as a database.

7. How to choose

  1. User, permissions, and dependencies for this run: use context.
  2. Messages and temporary results for the current flow: use state.
  3. Data still needed in the next request: use store.

Summary

ToolRuntime is the entry point for tools to obtain runtime information:

First determine the data's lifecycle, then choose the object, and your tool code will not become messy.