Skip to content

Commit 49af0b7

Browse files
committed
docs(tools): add best practices guide for function tools
Add comprehensive best practices guide covering: - File upload handling patterns (both uploaded files and file paths) - Type annotation requirements - Tool naming conventions - Docstring standards - Error handling patterns - Structured return values Addresses common pitfalls and provides examples for building robust function tools in ADK. Relocates content from google/adk-python#3193 as requested by @boyangsvl.
1 parent d0c6486 commit 49af0b7

File tree

1 file changed

+256
-0
lines changed

1 file changed

+256
-0
lines changed

docs/tools/best-practices.md

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
# Best Practices for Function Tools
2+
3+
This guide provides best practices and common patterns for building robust and user-friendly function tools in ADK.
4+
5+
## Handling File Uploads
6+
7+
When building agents that process files, you often need to handle both:
8+
9+
1. Files uploaded directly through the chat interface
10+
2. File paths provided as text by the user
11+
12+
### Problem
13+
14+
Without explicit handling, agents may:
15+
16+
- Ask for file paths even when files are already uploaded
17+
- Fail to recognize uploaded files as attachments
18+
- Pass incorrect parameters to parsing tools
19+
20+
### Solution: Flexible File Path Handling
21+
22+
Design your tools to accept both display names (from uploaded files) and full file paths.
23+
24+
???+ example "File Upload Tool Pattern"
25+
=== "Python"
26+
```python
27+
import os
28+
29+
def parse_document(doc_path: str = "uploaded_document") -> str:
30+
"""Parses a document (uploaded file or file path).
31+
32+
Args:
33+
doc_path: Display name of uploaded file or full file path.
34+
35+
Returns:
36+
Success message with output path.
37+
"""
38+
# Handle both uploaded file identifiers and full paths
39+
if '/' in doc_path:
40+
# Full path provided
41+
run_id = os.path.basename(os.path.dirname(doc_path))
42+
else:
43+
# Uploaded file display name or simple identifier
44+
run_id = doc_path.replace('.', '_')
45+
46+
output_path = f"output/{run_id}.xml"
47+
os.makedirs(os.path.dirname(output_path), exist_ok=True)
48+
49+
# ... your parsing logic here
50+
51+
return f"Successfully parsed document to {output_path}"
52+
```
53+
54+
### Agent Instructions
55+
56+
Your agent instructions should explicitly guide the LLM on how to handle both upload methods:
57+
58+
???+ example "Agent Instructions for File Handling"
59+
=== "Python"
60+
```python
61+
from google.adk import Agent
62+
63+
agent = Agent(
64+
name="DocumentParser",
65+
model="gemini-2.0-flash",
66+
instruction="""
67+
You are a document parsing agent.
68+
69+
When the user provides files:
70+
1. If files are uploaded directly in the chat:
71+
- Acknowledge them by their display names
72+
- Call parsing tools with their display names or identifiers
73+
- You can identify uploaded files by their presence as attachments
74+
2. If file paths are provided in text:
75+
- Call parsing tools with the exact paths provided
76+
77+
Do not ask for file paths if files are already uploaded.
78+
""",
79+
tools=[parse_document]
80+
)
81+
```
82+
83+
### Why This Matters
84+
85+
This pattern ensures your agent gracefully handles both upload methods, providing a better user experience.
86+
87+
#### Example Usage
88+
89+
**Uploaded File**:
90+
```
91+
User: [Uploads: report.pdf]
92+
Parse this document
93+
94+
Agent: [Calls parse_document("report.pdf")]
95+
```
96+
97+
**File Path**:
98+
```
99+
User: Parse the document at /path/to/reports/quarterly_report.pdf
100+
101+
Agent: [Calls parse_document("/path/to/reports/quarterly_report.pdf")]
102+
```
103+
104+
## Type Annotations
105+
106+
Always use explicit type annotations for function tool parameters to ensure proper schema generation.
107+
108+
!!! warning "Use Explicit Types"
109+
Bare `list` and `dict` types can cause schema validation errors. Always specify item types.
110+
111+
???+ example "Proper Type Annotations"
112+
=== "❌ Invalid"
113+
```python
114+
def process_items(items: list) -> str:
115+
"""This causes Gemini schema validation errors."""
116+
pass
117+
```
118+
=== "✅ Valid"
119+
```python
120+
from typing import List
121+
122+
def process_items(items: List[str]) -> str:
123+
"""Properly typed for Gemini schema generation."""
124+
pass
125+
```
126+
127+
## Tool Naming
128+
129+
Use clear, descriptive names for your tools that indicate their purpose:
130+
131+
-`get_weather_forecast`
132+
-`parse_pdf_document`
133+
-`calculate_tax_liability`
134+
-`tool1`
135+
-`process`
136+
-`do_thing`
137+
138+
## Docstrings
139+
140+
Provide comprehensive docstrings with `Args` and `Returns` sections. The LLM uses these to understand when and how to use your tool.
141+
142+
???+ example "Well-Documented Tool"
143+
=== "Python"
144+
```python
145+
def get_weather(city: str, unit: str = "celsius") -> dict:
146+
"""Retrieves current weather for a specified city.
147+
148+
This function fetches real-time weather data including temperature,
149+
humidity, and conditions for the requested city.
150+
151+
Args:
152+
city: The name of the city (e.g., "London", "New York").
153+
unit: Temperature unit, either "celsius" or "fahrenheit".
154+
Defaults to "celsius".
155+
156+
Returns:
157+
A dictionary containing:
158+
- temperature: Current temperature as a float
159+
- humidity: Humidity percentage as an integer
160+
- conditions: Weather conditions as a string
161+
- timestamp: UTC timestamp of the reading
162+
163+
Example:
164+
>>> get_weather("Paris", "celsius")
165+
{
166+
"temperature": 18.5,
167+
"humidity": 65,
168+
"conditions": "Partly cloudy",
169+
"timestamp": "2025-10-18T10:30:00Z"
170+
}
171+
"""
172+
# ... implementation
173+
```
174+
175+
## Error Handling
176+
177+
Return clear, actionable error messages that help the LLM understand what went wrong and how to fix it.
178+
179+
???+ example "Error Handling"
180+
=== "❌ Poor Error Handling"
181+
```python
182+
def validate_email(email: str) -> bool:
183+
if "@" not in email:
184+
raise ValueError("Invalid")
185+
return True
186+
```
187+
=== "✅ Good Error Handling"
188+
```python
189+
def validate_email(email: str) -> dict:
190+
"""Validates an email address format.
191+
192+
Args:
193+
email: The email address to validate.
194+
195+
Returns:
196+
A dictionary with 'valid' (bool) and 'message' (str) fields.
197+
"""
198+
if "@" not in email:
199+
return {
200+
"valid": False,
201+
"message": f"Email '{email}' is missing '@' symbol. "
202+
"Expected format: [email protected]"
203+
}
204+
205+
if "." not in email.split("@")[1]:
206+
return {
207+
"valid": False,
208+
"message": f"Email '{email}' domain is missing a TLD. "
209+
"Expected format: [email protected]"
210+
}
211+
212+
return {
213+
"valid": True,
214+
"message": f"Email '{email}' is valid."
215+
}
216+
```
217+
218+
## Return Values
219+
220+
Structure return values consistently and include enough context for the LLM to provide useful responses to the user.
221+
222+
???+ tip "Structured Returns"
223+
Return dictionaries with clear field names rather than plain strings or tuples when you have multiple pieces of information to convey.
224+
225+
=== "❌ Unclear Return"
226+
```python
227+
def search_products(query: str) -> str:
228+
return "Found 3 items: item1, item2, item3"
229+
```
230+
=== "✅ Structured Return"
231+
```python
232+
def search_products(query: str) -> dict:
233+
"""Searches for products matching the query.
234+
235+
Returns:
236+
A dictionary containing:
237+
- count: Number of products found
238+
- products: List of product dictionaries
239+
- query: The original search query
240+
"""
241+
return {
242+
"count": 3,
243+
"products": [
244+
{"id": 1, "name": "item1", "price": 19.99},
245+
{"id": 2, "name": "item2", "price": 29.99},
246+
{"id": 3, "name": "item3", "price": 39.99}
247+
],
248+
"query": query
249+
}
250+
```
251+
252+
## See Also
253+
254+
- [Function Tools](function-tools.md) - Learn how to create function tools
255+
- [Long Running Tools](function-tools.md#long-run-tool) - Handle tools that take time to execute
256+
- [Tool Performance](performance.md) - Optimize tool execution

0 commit comments

Comments
 (0)