Design Patterns and Best Practices for Callbacks¶
Callbacks offer powerful hooks into the agent lifecycle. Here are common design patterns illustrating how to leverage them effectively in ADK, followed by best practices for implementation.
Design Patterns¶
These patterns demonstrate typical ways to enhance or control agent behavior using callbacks:
1. Guardrails & Policy Enforcement¶
- Pattern: Intercept requests before they reach the LLM or tools to enforce rules.
- How: Use
before_model_callbackto inspect theLlmRequestprompt orbefore_tool_callbackto inspect tool arguments (args). If a policy violation is detected (e.g., forbidden topics, profanity), return a predefined response (LlmResponseordict) to block the operation and optionally updatecontext.stateto log the violation. - Example: A
before_model_callbackchecksllm_request.contentsfor sensitive keywords and returns a standard "Cannot process this request"LlmResponseif found, preventing the LLM call.
2. Dynamic State Management¶
- Pattern: Read from and write to session state within callbacks to make agent behavior context-aware and pass data between steps.
- How: Access
callback_context.stateortool_context.state. Modifications (state['key'] = value) are automatically tracked in the subsequentEvent.actions.state_deltafor persistence by theSessionService. - Example: An
after_tool_callbacksaves atransaction_idfrom the tool's result totool_context.state['last_transaction_id']. A laterbefore_agent_callbackmight readstate['user_tier']to customize the agent's greeting.
3. Logging and Monitoring¶
- Pattern: Add detailed logging at specific lifecycle points for observability and debugging.
- How: Implement callbacks (e.g.,
before_agent_callback,after_tool_callback,after_model_callback) to print or send structured logs containing information like agent name, tool name, invocation ID, and relevant data from the context or arguments. - Example: Log messages like
INFO: [Invocation: e-123] Before Tool: search_api - Args: {'query': 'ADK'}.
4. Caching¶
- Pattern: Avoid redundant LLM calls or tool executions by caching results.
- How: In
before_model_callbackorbefore_tool_callback, generate a cache key based on the request/arguments. Checkcontext.state(or an external cache) for this key. If found, return the cachedLlmResponseor resultdictdirectly, skipping the actual operation. If not found, allow the operation to proceed and use the correspondingafter_callback (after_model_callback,after_tool_callback) to store the new result in the cache using the key. - Example:
before_tool_callbackforget_stock_price(symbol)checksstate[f"cache:stock:{symbol}"]. If present, returns the cached price; otherwise, allows the API call andafter_tool_callbacksaves the result to the state key.
5. Request/Response Modification¶
- Pattern: Alter data just before it's sent to the LLM/tool or just after it's received.
- How:
before_model_callback: Modifyllm_request(e.g., add system instructions based onstate).after_model_callback: Modify the returnedLlmResponse(e.g., format text, filter content).before_tool_callback: Modify the toolargsdictionary.after_tool_callback: Modify thetool_responsedictionary.
- Example:
before_model_callbackappends "User language preference: Spanish" tollm_request.config.system_instructionifcontext.state['lang'] == 'es'.
6. Conditional Skipping of Steps¶
- Pattern: Prevent standard operations (agent run, LLM call, tool execution) based on certain conditions.
- How: Return a value from a
before_callback (Contentfrombefore_agent_callback,LlmResponsefrombefore_model_callback,dictfrombefore_tool_callback). The framework interprets this returned value as the result for that step, skipping the normal execution. - Example:
before_tool_callbackcheckstool_context.state['api_quota_exceeded']. IfTrue, it returns{'error': 'API quota exceeded'}, preventing the actual tool function from running.
7. Tool-Specific Actions (Authentication & Summarization Control)¶
- Pattern: Handle actions specific to the tool lifecycle, primarily authentication and controlling LLM summarization of tool results.
- How: Use
ToolContextwithin tool callbacks (before_tool_callback,after_tool_callback).- Authentication: Call
tool_context.request_credential(auth_config)inbefore_tool_callbackif credentials are required but not found (e.g., viatool_context.get_auth_responseor state check). This initiates the auth flow. - Summarization: Set
tool_context.actions.skip_summarization = Trueif the raw dictionary output of the tool should be passed back to the LLM or potentially displayed directly, bypassing the default LLM summarization step.
- Authentication: Call
- Example: A
before_tool_callbackfor a secure API checks for an auth token in state; if missing, it callsrequest_credential. Anafter_tool_callbackfor a tool returning structured JSON might setskip_summarization = True.
8. Artifact Handling¶
- Pattern: Save or load session-related files or large data blobs during the agent lifecycle.
- How: Use
callback_context.save_artifact/tool_context.save_artifactto store data (e.g., generated reports, logs, intermediate data). Useload_artifactto retrieve previously stored artifacts. Changes are tracked viaEvent.actions.artifact_delta. - Example: An
after_tool_callbackfor a "generate_report" tool saves the output file usingtool_context.save_artifact("report.pdf", report_part). Abefore_agent_callbackmight load a configuration artifact usingcallback_context.load_artifact("agent_config.json").
Best Practices for Callbacks¶
- Keep Focused: Design each callback for a single, well-defined purpose (e.g., just logging, just validation). Avoid monolithic callbacks.
- Mind Performance: Callbacks execute synchronously within the agent's processing loop. Avoid long-running or blocking operations (network calls, heavy computation). Offload if necessary, but be aware this adds complexity.
- Handle Errors Gracefully: Use
try...exceptblocks within your callback functions. Log errors appropriately and decide if the agent invocation should halt or attempt recovery. Don't let callback errors crash the entire process. - Manage State Carefully:
- Be deliberate about reading from and writing to
context.state. Changes are immediately visible within the current invocation and persisted at the end of the event processing. - Use specific state keys rather than modifying broad structures to avoid unintended side effects.
- Consider using state prefixes (
State.APP_PREFIX,State.USER_PREFIX,State.TEMP_PREFIX) for clarity, especially with persistentSessionServiceimplementations.
- Be deliberate about reading from and writing to
- Consider Idempotency: If a callback performs actions with external side effects (e.g., incrementing an external counter), design it to be idempotent (safe to run multiple times with the same input) if possible, to handle potential retries in the framework or your application.
- Test Thoroughly: Unit test your callback functions using mock context objects. Perform integration tests to ensure callbacks function correctly within the full agent flow.
- Ensure Clarity: Use descriptive names for your callback functions. Add clear docstrings explaining their purpose, when they run, and any side effects (especially state modifications).
- Use Correct Context Type: Always use the specific context type provided (
CallbackContextfor agent/model,ToolContextfor tools) to ensure access to the appropriate methods and properties.
By applying these patterns and best practices, you can effectively use callbacks to create more robust, observable, and customized agent behaviors in ADK.