|
| 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