LangChain LangGraph
Module 1 Module 2 Module 3
Module 3 · Middleware and HITL
1 3.2 Managing Msgs
2 3.3 HITL
3 3.4 Dyn Models
4 3.4 Dyn Prompts
5 3.4 Dyn Tools
6 3.5 Email Agent
SUM Module Summary
HomeLangChainModule 3 · Middleware and HITL3.4 Dyn Tools
📓 Notebook 📖 Explained
LANGCHAIN MODULE 3 · LESSON 3.4c

Dynamic Tool Permissions
User Role · request.override(tools=...)

This section restricts the tools that can be used according to the User role: internal User users can access the Data library, and external User users can only use Web Search.

1 Why dynamically restrict tools

Tool permissions cannot be restricted by prompt alone. A more reliable approach is to directly modify the visible tool list before the model adjusts Use, so that the model cannot see tools that should not be adjusted Use at all.

User roleUse tools
internalweb_search + sql_query
externalweb_search only
Core safety point

Don't just tell external users "Don't check the Data library". Data library tools should be removed from middleware.

2 Define Web Search and SQL tools

Notebook Prepare has two tools: web_search queries the Internet, and sql_query queries the local Chinook Data library.

Python
tavily_client = TavilyClient()
db = SQLDatabase.from_uri("sqlite:///resources/Chinook.db")

@tool
def web_search(query: str) -> Dict[str, Any]:
    """Search the web for information"""
    return tavily_client.search(query)

@tool
def sql_query(query: str) -> str:
    """Obtain information from the database using SQL queries"""
    try:
        return db.run(query)
    except Exception as e:
        return f"Error: {e}"
Database tool risk

SQL tools usually access internal Data. Production environments also require read-only restrictions, table-level permissions, row limit and audit logs.

3 Define user-role context

User role passed in through runtime context. The default role is external.

Python
from dataclasses import dataclass

@dataclass
class UserRole:
    user_role: str = "external"
Why use context

User roles usually come from login systems, organizational permissions, or API tokens and should not be left to the model to determine from the User user text.

4 Use middleware to override the tool list

dynamic_tool_call reads request.runtime.context.user_role. If it is not internal, use request.override(tools=[web_search]) Put SQL tool to remove it.

Python
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable

@wrap_model_call
def dynamic_tool_call(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    """Dynamically call tools based on the runtime context"""
    user_role = request.runtime.context.user_role

    if user_role == "internal":
        pass
    else:
        tools = [web_search]
        request = request.override(tools=tools)

    return handler(request)
Hard limit

This model adjustment for external users will only seeweb_search, so it cannot be selectedsql_query

5 Create the Agent and test an external user

Agent initially registers two tools, but the actual visible tools will be rewritten by middleware according to roles. When an external Use user asks for the number of Data libraries, the model does not have a SQL tool to use.

Python
agent = create_agent(
    model=deepseek_model(),
    tools=[web_search, sql_query],
    middleware=[dynamic_tool_call],
    context_schema=UserRole,
)

response = agent.invoke(
    {"messages": [HumanMessage(content="How many artists are in the database?")]},
    context={"user_role": "external"},
)

print(response["messages"][-1].content)
Testing internal users

If you passcontext=UserRole(user_role="internal"), the middleware model can enable Use SQL tools without overriding the tool list.

6 Lesson takeaway