Tool Hooks
Hooks führen vor und nach jedem Tool-Aufruf eigene Logik aus: Parameter validieren, Zugriffskontrollen durchsetzen, Ergebnisse verändern und Tool-Nutzung protokollieren.
Auf dieser Seite
Was sind Tool Hooks?
Hooks, die vor und nach jedem Tool-Aufruf innerhalb eines Agenten ausgeführt werden
Tool-Hooks sind Python-Funktionen, die jeden Tool-Aufruf eines Agenten umschließen. Während Middleware einmal vor und nach der gesamten Ausführung des Agenten läuft, werden Tool-Hooks um jeden einzelnen Tool-Aufruf ausgeführt. Nutze sie für Zugriffskontrolle, Parametervalidierung, Ergebnistransformation und Logging.
Erstelle im Verzeichnis hooks/ eine Datei mit demselben Namen wie der Agent. So gilt hooks/order-manager.py beispielsweise für den Agenten, dessen YAML name: order-manager enthält. Eine YAML-Konfiguration ist nicht erforderlich. Middleware verwendet dieselbe Namenskonvention.
Ausführungsablauf
Jeder Tool-Aufruf ruft before(), dann das Tool und anschließend after() auf:
Ruft der Agent in einem Run mehrere Tools auf, werden die Hooks für jedes davon unabhängig ausgelöst.
Middleware umschließt den gesamten Agenten-Run – von der Anfrage bis zur Antwort. Tool-Hooks umschließen jeden einzelnen Tool-Aufruf innerhalb dieses Runs. Beides kann für denselben Agenten verwendet werden.
Einfacher Tool Hook
"""Tool hooks for the order-manager agent."""
from typing import Any
from connic import AbortTool
async def before(tool_name: str, params: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]:
"""
Called before every tool call.
Args:
tool_name: Name of the tool about to run (e.g. "get_order")
params: Dict of parameters the LLM chose for the tool
context: Shared run context dict (see Context docs)
Returns:
Parameter keys to add or replace. Return None to keep the original parameters.
"""
# Block deletions for non-admin users
if tool_name == "delete_order" and not context.get("is_admin"):
raise AbortTool({"error": "Permission denied: only admins can delete orders"})
return params
async def after(tool_name: str, params: dict[str, Any], result: Any, context: dict[str, Any]) -> Any:
"""
Called after every tool call.
Args:
tool_name: Name of the tool that just ran
params: The parameters the tool was called with
result: The tool's return value
context: Shared run context dict (see Context docs)
Returns:
Modified result (or original unchanged)
"""
print(f"[hook] {tool_name}({params}) -> {result}")
return resultBeide Hooks sind optional. Es kann nur before(), nur after() oder beides definiert werden. Auch der Parameter context ist optional. Lass ihn weg, wenn keine Run-Metadaten benötigt werden.
Funktionssignaturen
before()
| Parameter | Typ | Beschreibung |
|---|---|---|
| tool_name | str | Name des Tools, das gleich ausgeführt wird |
| params | dict | Parameter, die das LLM für den Tool-Aufruf ausgewählt hat |
| context | dict (optional) | Gemeinsamer Run-Kontext, dasselbe Dictionary wie in Middleware und Tools |
Rückgabewert: ein Dictionary mit Parameterschlüsseln, die hinzugefügt oder ersetzt werden. Gib None zurück, um die ursprünglichen Parameter unverändert zu lassen.
after()
| Parameter | Typ | Beschreibung |
|---|---|---|
| tool_name | str | Name des gerade ausgeführten Tools |
| params | dict | Parameter, mit denen das Tool aufgerufen wurde |
| result | Any | Rückgabewert des Tools |
| context | dict (optional) | Gemeinsamer Run-Kontext |
Rückgabewert: das Ergebnis, das an das LLM zurückgegeben wird. Gib None zurück, um das ursprüngliche Ergebnis unverändert zu lassen.
Tool mit AbortTool überspringen
Löse AbortTool in before() aus, um das Tool vollständig zu überspringen und dem LLM ein eigenes Ergebnis zurückzugeben. Das Tool wird nicht ausgeführt und der Trace markiert den Call als Fehler.
"""Block specific tools based on runtime conditions."""
from typing import Any
from connic import AbortTool
async def before(tool_name: str, params: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]:
# Block destructive tools outside business hours
from datetime import datetime
hour = datetime.now().hour
destructive = {"delete_order", "db_delete", "cancel_subscription"}
if tool_name in destructive and not (9 <= hour < 17):
raise AbortTool({
"error": f"Tool '{tool_name}' is only available during business hours (9am-5pm)"
})
return paramsAbortTool überspringt nur den aktuellen Tool-Aufruf: Der Agent läuft weiter und kann andere Tools aufrufen oder antworten. StopProcessing bricht den gesamten Agenten-Run sofort ab.
Häufige Use Cases
Parameter validieren und normalisieren
Bereinige Tool-Parameter vor der Ausführung oder erzwinge Standardwerte.
"""Normalise and validate tool parameters."""
from typing import Any
async def before(tool_name: str, params: dict[str, Any]) -> dict[str, Any]:
# Normalise order IDs to uppercase
if "order_id" in params:
params["order_id"] = params["order_id"].upper()
# Enforce default limit on search tools
if tool_name == "search_orders" and "limit" not in params:
params["limit"] = 10
return paramsSensible Daten aus Ergebnissen entfernen
Entferne PII oder sensible Felder aus Tool-Ergebnissen, bevor sie das LLM erreichen.
"""Redact sensitive data from tool results."""
from typing import Any
import re
async def after(tool_name: str, params: dict[str, Any], result: Any, context: dict[str, Any]) -> Any:
# Redact email addresses from results
if isinstance(result, str):
return re.sub(r'[\w.-]+@[\w.-]+\.\w+', '[REDACTED]', result)
if isinstance(result, dict) and "email" in result:
result["email"] = "[REDACTED]"
return resultRun mit StopProcessing abbrechen
Verwende StopProcessing, wenn sich aus einem Tool-Aufruf ergibt, dass der gesamte Run beendet werden soll.
"""Use StopProcessing to abort the entire run from a hook."""
from typing import Any
from connic import StopProcessing
async def before(tool_name: str, params: dict[str, Any], context: dict[str, Any]) -> dict[str, Any]:
# If a critical tool fails validation, stop the entire run
if tool_name == "charge_customer":
amount = params.get("amount", 0)
if amount > 10000:
raise StopProcessing("Transaction blocked: amount exceeds safety limit")
return paramsTool Calls protokollieren
"""Log all tool calls to an external service."""
from typing import Any
import httpx
async def after(tool_name: str, params: dict[str, Any], result: Any, context: dict[str, Any]) -> Any:
try:
async with httpx.AsyncClient() as client:
await client.post("https://logs.internal/tool-calls", json={
"run_id": context.get("run_id"),
"agent": context.get("agent_name"),
"tool": tool_name,
"params": params,
})
except Exception:
pass # Don't fail the tool call if logging fails
return resultAusgaben über print und logging aus der Python-Standardbibliothek werden in Hooks genauso erfasst wie in Tools und Middleware. Sie erscheinen im Projekt auf dem Tab Logs und in der Run-Detailansicht mit der Quelle hook.<tool_name>. So lassen sich Tool-Aufrufe nachvollziehen, ohne Log-Daten an einen externen Dienst zu senden.
Optionaler Kontext-Parameter
Der Parameter context wird anhand der Funktionssignatur automatisch erkannt. Füge ihn für Run-Metadaten hinzu und lass ihn für einfachere Hooks weg. Dabei gilt dieselbe Konvention wie für Custom Tools.
"""The context parameter is optional."""
from typing import Any
# Without context - simpler signature
async def before(tool_name: str, params: dict[str, Any]) -> dict[str, Any]:
return params
# With context - access run metadata and middleware values
async def after(tool_name: str, params: dict[str, Any], result: Any, context: dict[str, Any]) -> Any:
run_id = context.get("run_id")
return resultProjekt-Struktur
Sync- und Async-Unterstützung
"""Sync hooks also work."""
from typing import Any
def before(tool_name: str, params: dict[str, Any]) -> dict[str, Any]:
"""Sync functions are automatically handled."""
if "query" in params:
params["query"] = params["query"].strip()
return params
def after(tool_name: str, params: dict[str, Any], result: Any) -> Any:
"""Both sync and async are supported."""
return resultVerwende Async-Funktionen für I/O-Operationen (API-Aufrufe, Datenbankabfragen). Für einfache Validierungen und Transformationen sind Sync-Funktionen geeignet.
Löst ein Hook eine unbehandelte Exception aus – also weder AbortTool noch StopProcessing –, schlägt der Tool-Aufruf fehl. Der Fehler wird an das LLM zurückgegeben, das den Call wiederholen oder mit einer Fehlermeldung antworten kann. Der Trace erfasst die Exception.
Tool-Hooks gelten für alle in tools/ definierten Tools, für vordefinierte Tools und für API Spec Tools. Sie gelten nicht für Tools, die von externen MCP Servern bereitgestellt werden.