Skip to content

Commit 19f5246

Browse files
GWealecopybara-github
authored andcommitted
docs: Update ADK agent builder instructions for model callback signatures
PiperOrigin-RevId: 824690587
1 parent 1ca8206 commit 19f5246

1 file changed

Lines changed: 48 additions & 8 deletions

File tree

contributing/samples/adk_agent_builder_assistant/instruction_embedded.template

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ When users ask informational questions like "find me examples", "show me samples
1616

1717
**NON-NEGOTIABLE**: `root_agent.yaml` MUST always declare `agent_class: LlmAgent`.
1818
**NEVER** set `root_agent.yaml` to any workflow agent type (SequentialAgent,
19-
ParallelAgent, LoopAgent). All workflow coordination must stay in sub-agents, not the root file.
19+
ParallelAgent, LoopAgent.) All workflow coordination must stay in sub-agents, not the root file.
2020
**MODEL CONTRACT**: Every `LlmAgent` (root and sub-agents) must explicitly set
2121
`model` to the confirmed model choice (use `{default_model}` only when the user
2222
asks for the default). Never omit this field or rely on a global default.
@@ -347,6 +347,12 @@ uncertainty about architecture, or you otherwise need authoritative guidance.
347347
8. **Follow current ADK patterns**: Always search for and reference the latest examples from contributing/samples
348348
9. **Gemini API Usage**: If generating Python code that interacts with Gemini models, use `import google.genai as genai`, not `google.generativeai`.
349349

350+
### ✅ Fully Qualified Paths Required
351+
- Every tool or callback reference in YAML must be a fully qualified dotted path that starts with the project folder name. Use `{project_folder_name}.callbacks.privacy_callbacks.censor_content`, **never** `callbacks.privacy_callbacks.censor_content`.
352+
- Only reference packages that actually exist. Before you emit a dotted path, confirm the directory contains an `__init__.py` so Python can import it. Create `__init__.py` files for each subdirectory that should be importable (for example `callbacks/` or `tools/`). The project root itself does not need an `__init__.py`.
353+
- When you generate Python modules with `write_files`, make sure the tool adds these `__init__.py` markers for the package directories (skip the project root) so future imports succeed.
354+
- If the user already has bare paths like `callbacks.foo`, explain why they must be rewritten with the project prefix and add the missing `__init__.py` files when you generate the Python modules.
355+
350356
### 🚨 CRITICAL: Callback Correct Signatures
351357
ADK supports different callback types with DIFFERENT signatures. Use FUNCTION-based callbacks (never classes):
352358

@@ -378,17 +384,49 @@ from google.adk.models.llm_request import LlmRequest
378384
from google.adk.models.llm_response import LlmResponse
379385
from google.adk.agents.callback_context import CallbackContext
380386

381-
def log_model_request(callback_context: CallbackContext, request: LlmRequest) -> Optional[LlmResponse]:
387+
def log_model_request(
388+
*, callback_context: CallbackContext, llm_request: LlmRequest
389+
) -> Optional[LlmResponse]:
382390
"""Before model callback to log requests."""
383-
print(f"Model request: {{request.contents}}")
391+
print(f"Model request: {{llm_request.contents}}")
384392
return None # Return None to proceed with original request
385393

386-
def modify_model_response(callback_context: CallbackContext, response: LlmResponse) -> Optional[LlmResponse]:
394+
from google.adk.events.event import Event
395+
396+
def modify_model_response(
397+
*,
398+
callback_context: CallbackContext,
399+
llm_response: LlmResponse,
400+
model_response_event: Optional[Event] = None,
401+
) -> Optional[LlmResponse]:
387402
"""After model callback to modify response."""
388-
# Modify response if needed
389-
return response # Return modified response or None for original
403+
_ = callback_context # Access context if you need state or metadata
404+
_ = model_response_event # Available for tracing and event metadata
405+
if (
406+
not llm_response
407+
or not llm_response.content
408+
or not llm_response.content.parts
409+
):
410+
return llm_response
411+
412+
updated_parts = []
413+
for part in llm_response.content.parts:
414+
text = getattr(part, "text", None)
415+
if text:
416+
updated_parts.append(
417+
types.Part(text=text.replace("dolphins", "[CENSORED]"))
418+
)
419+
else:
420+
updated_parts.append(part)
421+
422+
llm_response.content = types.Content(
423+
parts=updated_parts, role=llm_response.content.role
424+
)
425+
return llm_response
390426
```
391427

428+
**Callback content handling**: `LlmResponse` exposes a single `content` field (a `types.Content`). ADK already extracts the first candidate for you and does not expose `llm_response.candidates`. When filtering or rewriting output, check `llm_response.content` and mutate its `parts`. Preserve non-text parts and reassign a new `types.Content` rather than mutating undefined attributes.
429+
392430
## 3. Tool Callbacks (before_tool_callbacks / after_tool_callbacks)
393431

394432
**✅ CORRECT Tool Callback:**
@@ -412,11 +450,13 @@ def log_tool_result(tool: BaseTool, tool_args: Dict[str, Any], tool_context: Too
412450

413451
## Callback Signature Summary:
414452
- **Agent Callbacks**: `(callback_context: CallbackContext) -> Optional[types.Content]`
415-
- **Before Model**: `(callback_context: CallbackContext, request: LlmRequest) -> Optional[LlmResponse]`
416-
- **After Model**: `(callback_context: CallbackContext, response: LlmResponse) -> Optional[LlmResponse]`
453+
- **Before Model**: `(*, callback_context: CallbackContext, llm_request: LlmRequest) -> Optional[LlmResponse]`
454+
- **After Model**: `(*, callback_context: CallbackContext, llm_response: LlmResponse, model_response_event: Optional[Event] = None) -> Optional[LlmResponse]`
417455
- **Before Tool**: `(tool: BaseTool, tool_args: Dict[str, Any], tool_context: ToolContext) -> Optional[Dict]`
418456
- **After Tool**: `(tool: BaseTool, tool_args: Dict[str, Any], tool_context: ToolContext, result: Dict) -> Optional[Dict]`
419457

458+
**Name Matching Matters**: ADK passes callback arguments by keyword. Always name parameters exactly `callback_context`, `llm_request`, `llm_response`, and `model_response_event` (when used) so they bind correctly. Returning `None` keeps the original value; otherwise return the modified `LlmResponse`.
459+
420460
## Important ADK Requirements
421461

422462
**File Naming & Structure:**

0 commit comments

Comments
 (0)