Skip to main content
The BEFORE_TOOL_DEFINITIONS hook fires right after the tool list is assembled for a request and before it reaches the LLM — letting you drop tools, rewrite descriptions, or constrain parameter schemas on the fly.
For the full hook lifecycle model, see the Hooks concept page.
from praisonaiagents import Agent
from praisonaiagents.hooks import HookEvent, BeforeToolDefinitionsInput

def hide_dangerous(data: BeforeToolDefinitionsInput) -> None:
    data.tool_definitions[:] = [
        t for t in data.tool_definitions
        if t.get("function", {}).get("name") != "execute_shell"
    ]

agent = Agent(
    name="Safe Agent",
    instructions="Help with safe tasks only.",
    hooks={HookEvent.BEFORE_TOOL_DEFINITIONS: hide_dangerous},
)

agent.start("What can you do?")
The user prompts the agent; the hook trims tool definitions before the LLM sees them.

Quick Start

1

Drop a Tool by Name

Remove a tool from the LLM’s view for every request — the tool stays registered on the agent but the model never sees it:
from praisonaiagents import Agent
from praisonaiagents.hooks import HookEvent, BeforeToolDefinitionsInput

def hide_dangerous_tools(data: BeforeToolDefinitionsInput) -> None:
    data.tool_definitions[:] = [
        t for t in data.tool_definitions
        if t.get("function", {}).get("name") != "execute_shell"
    ]

agent = Agent(
    name="Safe Agent",
    instructions="Help users with safe tasks only.",
    hooks={HookEvent.BEFORE_TOOL_DEFINITIONS: hide_dangerous_tools},
)

agent.start("What can you do?")
2

Rewrite a Tool Description

Append usage guidance to a tool description before it reaches the model:
from praisonaiagents import Agent
from praisonaiagents.hooks import HookEvent, BeforeToolDefinitionsInput

def annotate_tools(data: BeforeToolDefinitionsInput) -> None:
    for tool in data.tool_definitions:
        fn = tool.get("function", {})
        if fn.get("name") == "web_search":
            fn["description"] = (
                fn.get("description", "") +
                " Always cite the URL in your answer."
            )

agent = Agent(
    name="Researcher",
    instructions="Search the web and cite sources.",
    hooks={HookEvent.BEFORE_TOOL_DEFINITIONS: annotate_tools},
)

agent.start("What is the latest on AI safety?")
3

Async Hook

The hook fires on both sync and async agent paths. Use an async function when you need async lookups:
import asyncio
from praisonaiagents import Agent
from praisonaiagents.hooks import HookEvent, BeforeToolDefinitionsInput

async def filter_by_session(data: BeforeToolDefinitionsInput) -> None:
    allowed = await fetch_allowed_tools(data.session_id)
    data.tool_definitions[:] = [
        t for t in data.tool_definitions
        if t.get("function", {}).get("name") in allowed
    ]

agent = Agent(
    name="Session-Scoped Agent",
    instructions="Operate within your session's permissions.",
    hooks={HookEvent.BEFORE_TOOL_DEFINITIONS: filter_by_session},
)
4

As a Plugin

Plugins implement before_tool_definitions() as a method:
from praisonaiagents import Agent, Plugin
from praisonaiagents.hooks import BeforeToolDefinitionsInput

class ToolFilterPlugin(Plugin):
    def before_tool_definitions(self, data: BeforeToolDefinitionsInput) -> None:
        data.tool_definitions[:] = [
            t for t in data.tool_definitions
            if not t.get("function", {}).get("name", "").startswith("internal_")
        ]

agent = Agent(
    name="Plugin Agent",
    instructions="Help the user.",
    plugins=[ToolFilterPlugin()],
)

How It Works

Deep copy guarantee: The agent deep-copies tool_definitions before passing them to the hook. This means:
  • Hook mutations cannot accidentally poison the agent’s internal tool cache.
  • Each request starts from the clean, registered tool list.
  • Only in-place mutations (data.tool_definitions[:] = ...) are adopted.

BeforeToolDefinitionsInput Fields

FieldTypeDescription
tool_definitionsList[Dict[str, Any]]The assembled OpenAI-style tool definitions. Mutate in-place.
modelstrThe LLM model name for this request
agent_namestrName of the agent firing the hook
session_idstrSession identifier (for per-session filtering)

Mutation Pattern

Always mutate tool_definitions in-place with slice assignment. Reassigning the variable does nothing:
def hook(data: BeforeToolDefinitionsInput) -> None:
    # ✅ Correct — slice assignment mutates in-place
    data.tool_definitions[:] = [
        t for t in data.tool_definitions if keep(t)
    ]

    # ❌ Wrong — reassigning the variable is ignored by the runtime
    data.tool_definitions = [t for t in data.tool_definitions if keep(t)]

Best Practices

The session_id field lets you load per-session permissions from a store and filter tools accordingly. This is the recommended pattern for multi-tenant bots where different users get different tool sets.
Removing a tool stops the model from calling it but can break agent plans that depend on it. Rewriting the description (e.g. adding "(disabled for this session)") lets the model know the tool exists but should not be called.
The hook fires before every LLM call. Avoid slow I/O in sync hooks; use async hooks for database or network lookups.
Because the runtime deep-copies before calling your hook, changes to one request’s tool list never affect the next. You cannot cache mutations across requests via the hook input itself.

Hooks (Concept)

Full hook lifecycle model

Hook Events

All available hook events

Bot Lifecycle Hooks

Hooks for bot startup and shutdown

Tool Availability

Control which tools agents can use