Hooks
Hooks provide a powerful way to transform inputs before they reach your agent and outputs before they're returned to the user. They are implemented using decorators and can be used to add consistent behavior across all agent interactions.
Overview
Hooks come in two varieties:
@hook.input: Transforms input before it reaches your agent@hook.output: Transforms output before it's returned to the user
Multiple hooks can be chained together and will be executed in order of their definition.
Basic Usage
Here's a simple example of using hooks to add prefixes and suffixes:
from agenty import Agent, hook
class MyAgent(Agent[str, str]):
input_schema = str
output_schema = str
@hook.input
def add_prefix(self, input: str) -> str:
return f"prefix_{input}"
@hook.output
def add_suffix(self, output: str) -> str:
return f"{output}_suffix"
When this agent runs:
- The input hook will transform
hellointoprefix_hellobefore processing - The output hook will transform the agent's response by adding
_suffix
Multiple Hooks
You can define multiple input and output hooks. They are executed in order of definition:
class AgentWithMultipleHooks(Agent[str, str]):
@hook.input
def first_input_hook(self, input: str) -> str:
return f"first_{input}"
@hook.input
def second_input_hook(self, input: str) -> str:
return f"second_{input}"
@hook.output
def first_output_hook(self, output: str) -> str:
return f"{output}_first"
@hook.output
def second_output_hook(self, output: str) -> str:
return f"{output}_second"
For an input of test, the processing flow is:
-
Input processing:
first_input_hookruns first:test→first_testsecond_input_hookruns next:first_test→second_first_test
-
Output processing:
first_output_hookruns first:result→result_firstsecond_output_hookruns next:result_first→result_first_second
Instance Attributes
Hooks can access instance attributes of your agent class, allowing for configurable behavior:
class AgentWithState(Agent[str, str]):
prefix = "default"
@hook.input
def add_custom_prefix(self, input: str) -> str:
return f"{self.prefix}_{input}"
For an example of using instance attributes in hooks, see the Wizard Agent example which uses a configurable fruit attribute.
Type Safety
Hooks must preserve the input and output types defined by your agent's type parameters. For example, if your agent is defined as Agent[str, float], all input hooks must accept and return strings, while output hooks must accept and return floats:
class AgentWithHooks(Agent[str, float]):
@hook.input
def invalid_hook(self, input: str) -> int: # This will raise an error
return 42
Hooks also support structured data types, making it easy to process complex data structures:
class AgentWithHooks(Agent[List[str], str]):
@hook.input
def list_hook(self, input: List[str]) -> List[str]:
new_list = []
for item in input:
# do stuff here
new_list.append(f"hook_{item}")
return new_list
Wizard Agent Example
For a complete example that demonstrates hooks in action, check out our Wizard Agent. This example shows how to:
- Enforce prerequisites (requiring an offering)
- Add consistent formatting (dramatic flair)
- Transform both inputs and outputs
- Chain multiple transformations together
- Use instance attributes for configuration
Here's a key excerpt from the example:
WIZARD_PROMPT = (
"You are a wise wizard. Answer questions only if first offered an apple."
)
class HookWizardAgent(Agent[str, str]):
system_prompt = WIZARD_PROMPT
fruit = "apple"
@hook.input
def add_apple(self, input: str) -> str:
return f"I've brought a {self.fruit}. Please answer my question: {input}"
@hook.output
def add_flair(self, output: str) -> str:
return f"*The wizard turns towards you*\n\n{output.strip()}"
@hook.output
def add_more_flair(self, output: str) -> str:
return f"*He opens his eyes...*\n\n{output.strip()}"
Best Practices
- Keep hooks focused and single-purpose
- Use descriptive names that indicate the hook's transformation
- Maintain type consistency between input and output
- Consider hook order when using multiple transformations
- Use instance attributes for configurable behavior
- Test hooks thoroughly to ensure they maintain data integrity
- Document hook behavior, especially when chaining multiple hooks