Tutorial 03: OpenAPI Tools - Connect Your Agent to Web APIs
Overviewβ
Connect your agents to the entire web! Learn how to automatically generate tools from OpenAPI specifications, enabling your agent to interact with REST APIs without writing tool functions manually.
What You'll Build: A Chuck Norris fact assistant that:
- Searches for Chuck Norris jokes by category
- Gets random jokes
- Lists available categories
- Uses OpenAPIToolset to auto-generate tools from API specification
Why It Matters: Instead of manually writing tool functions for every API endpoint, OpenAPI specifications let ADK auto-generate tools, saving time and reducing errors.
Prerequisitesβ
- Python 3.9+
google-adkinstalled- Google API key
- Completed Tutorials 01-02 (basics)
- Basic understanding of REST APIs
Core Conceptsβ
What is OpenAPI?β
OpenAPI (formerly Swagger) is a specification format for describing REST APIs:
{
"openapi": "3.0.0",
"paths": {
"/jokes/random": {
"get": {
"summary": "Get random joke",
"parameters": [...]
}
}
}
}
How OpenAPIToolset Worksβ
OpenAPI Spec β ADK Auto-Generation β Tools Available to Agent
Example:
toolset = OpenAPIToolset(spec=api_spec)
# ADK automatically creates:
# - get_jokes_random()
# - get_jokes_search()
# - get_jokes_categories()
Benefits:
- β No manual tool writing
- β Always matches API specification
- β Handles authentication automatically
- β Validates parameters
- β Works with any OpenAPI-compliant API
Use Case: Chuck Norris Fact Assistantβ
Scenario: Build an agent that retrieves Chuck Norris jokes/facts from the public Chuck Norris API.
Why This API?:
- β Free, no API key required
- β Simple OpenAPI specification
- β Great for learning
- β Fun and engaging
API: https://api.chucknorris.io/
Implementation: tutorial_implementation/tutorial03 - Complete working example with tests
Implementationβ
Project Structureβ
chuck_norris_agent/
βββ __init__.py
βββ agent.py
βββ .env
βββ README.md
Complete Codeβ
chuck_norris_agent/init.py:
from .agent import root_agent
__all__ = ['root_agent']
chuck_norris_agent/agent.py:
"""
Chuck Norris Fact Assistant - OpenAPI Tools Demonstration
This agent demonstrates how to use OpenAPIToolset to automatically
generate tools from an API specification without writing tool functions.
"""
from google.adk.agents import Agent
from google.adk.tools.openapi_tool import OpenAPIToolset
# ============================================================================
# OPENAPI SPECIFICATION
# ============================================================================
# Chuck Norris API OpenAPI Specification
# Based on: https://api.chucknorris.io/
CHUCK_NORRIS_SPEC = {
"openapi": "3.0.0",
"info": {
"title": "Chuck Norris API",
"description": "Free JSON API for hand curated Chuck Norris facts",
"version": "1.0.0"
},
"servers": [
{
"url": "https://api.chucknorris.io/jokes"
}
],
"paths": {
"/random": {
"get": {
"operationId": "get_random_joke",
"summary": "Get a random Chuck Norris joke",
"description": "Retrieve a random joke from the database. Can optionally filter by category.",
"parameters": [
{
"name": "category",
"in": "query",
"description": "Filter jokes by category (optional)",
"required": False,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"icon_url": {"type": "string"},
"id": {"type": "string"},
"url": {"type": "string"},
"value": {"type": "string"}
}
}
}
}
}
}
}
},
"/search": {
"get": {
"operationId": "search_jokes",
"summary": "Search for jokes",
"description": "Free text search for jokes containing the query term.",
"parameters": [
{
"name": "query",
"in": "query",
"description": "Search query (3+ characters required)",
"required": True,
"schema": {
"type": "string",
"minLength": 3
}
}
],
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"total": {"type": "integer"},
"result": {
"type": "array",
"items": {
"type": "object",
"properties": {
"icon_url": {"type": "string"},
"id": {"type": "string"},
"url": {"type": "string"},
"value": {"type": "string"}
}
}
}
}
}
}
}
}
}
}
},
"/categories": {
"get": {
"operationId": "get_categories",
"summary": "Get all joke categories",
"description": "Retrieve list of available joke categories.",
"responses": {
"200": {
"description": "Successful response",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
# ============================================================================
# OPENAPI TOOLSET
# ============================================================================
# Create OpenAPIToolset from specification
# ADK will automatically generate 3 tools:
# - get_random_joke(category: Optional[str])
# - search_jokes(query: str)
# - get_categories()
chuck_norris_toolset = OpenAPIToolset(spec_dict=CHUCK_NORRIS_SPEC)
# ============================================================================
# AGENT DEFINITION
# ============================================================================
root_agent = Agent(
name="chuck_norris_agent",
model="gemini-2.0-flash",
description="""
Chuck Norris fact assistant that can retrieve jokes/facts from the
Chuck Norris API using OpenAPI tools.
""",
instruction="""
You are a fun Chuck Norris fact assistant!
CAPABILITIES:
- Get random Chuck Norris jokes (optionally filtered by category)
- Search for jokes containing specific keywords
- List all available joke categories
STYLE:
- Be enthusiastic and playful
- Chuck Norris jokes are exaggerated for comedic effect
- Format jokes clearly for easy reading
- If search returns multiple results, show a few best ones
WORKFLOW:
- For random requests β use get_random_joke
- For specific topics β use search_jokes with query
- To see categories β use get_categories
- For category-specific random β use get_random_joke with category parameter
IMPORTANT:
- Always extract the 'value' field from API response (that's the actual joke)
- If search finds 0 results, suggest trying a different keyword
- Categories are lowercase (e.g., "dev", "movie", "food")
""",
# Pass the toolset to the agent
tools=[chuck_norris_toolset]
)
chuck_norris_agent/.env:
GOOGLE_GENAI_USE_VERTEXAI=FALSE
GOOGLE_API_KEY=your_api_key_here
Running the Agentβ
Demo in Actionβ
Here's what your Chuck Norris agent looks like in action:

Method 1: Web UI (Recommended)β
cd /path/to/chuck_norris_agent/..
adk web chuck_norris_agent
Try these prompts:
- "Tell me a random Chuck Norris joke"
- "Find jokes about computers"
- "What joke categories exist?"
- "Give me a random movie joke"
- "Search for jokes with the word 'code'"
Method 2: Terminalβ
adk run chuck_norris_agent
Expected Behaviorβ
Example 1: Random Jokeβ
User: "Tell me a random Chuck Norris joke"
Agent Process:
- Calls
get_random_joke()tool (no category) - API returns JSON with 'value' field
- Agent extracts and formats joke
Agent Response:
Here's a Chuck Norris fact for you:
"Chuck Norris can divide by zero."
π Classic!
Example 2: Search by Keywordβ
User: "Find jokes about programming"
Agent Process:
- Calls
search_jokes(query="programming")tool - API returns array of matching jokes
- Agent selects best ones and formats
Agent Response:
I found some programming-related Chuck Norris jokes:
1. "Chuck Norris writes code that optimizes itself."
2. "Chuck Norris doesn't use debuggers. Bugs confess to him."
3. "Chuck Norris can write infinite recursion that terminates."
π Developer humor!
Example 3: List Categoriesβ
User: "What categories are available?"
Agent Process:
- Calls
get_categories()tool - API returns array of category strings
- Agent formats list
Agent Response:
Chuck Norris jokes are available in these categories:
- animal
- career
- celebrity
- dev
- explicit
- fashion
- food
- history
- money
- movie
- music
- political
- religion
- science
- sport
- travel
Try asking for a joke from any category! For example: "Give me a random dev joke"
Example 4: Category-Specific Randomβ
User: "Tell me a joke from the dev category"
Agent Process:
- Calls
get_random_joke(category="dev")tool - API returns random joke from 'dev' category
- Agent formats joke
Agent Response:
Here's a dev joke for you:
"Chuck Norris's keyboard doesn't have a Ctrl key because nothing controls Chuck Norris."
π€ Perfect for developers!
How It Works: OpenAPIToolset Deep Diveβ
1. Specification Structureβ
{
"paths": {
"/random": { // Endpoint path
"get": { // HTTP method
"operationId": "...", // Becomes function name
"parameters": [...] // Becomes function args
}
}
}
}
2. Auto-Generated Toolsβ
From Spec:
{
"operationId": "search_jokes",
"parameters": [{ "name": "query", "required": true }]
}
ADK Creates:
async def search_jokes(query: str) -> Dict:
"""Search for jokes"""
# ADK handles HTTP request
response = requests.get(
"https://api.chucknorris.io/jokes/search",
params={"query": query}
)
return response.json()
3. Agent Tool Usageβ
The Agent constructor accepts toolsets directly - ADK handles the async tool loading internally:
root_agent = Agent(
...,
tools=[chuck_norris_toolset] # Pass toolset directly, not get_tools()
)
User: "Find jokes about code"
β
Agent (LLM): Decides to call search_jokes
β
search_jokes(query="code") executes
β
HTTP GET https://api.chucknorris.io/jokes/search?query=code
β
API returns: {"total": 5, "result": [...]}
β
Agent (LLM): Formats response for user
β
User sees: "I found 5 jokes about code: ..."
4. What ADK Handles Automaticallyβ
- β HTTP request construction
- β Parameter validation (type, required/optional)
- β URL building (server + path + query params)
- β Response parsing (JSON to dict)
- β Error handling (network, HTTP errors)
- β Authentication (if specified in spec)
Key Takeawaysβ
- OpenAPIToolset = Zero Manual Tool Code: No need to write
def search_jokes()yourself - operationId β Function Name: Controls how LLM sees the tool
- parameters β Function Args: Becomes tool function signature
- Works with Any OpenAPI API: GitHub, Stripe, Twilio, custom APIs
- No API Key Needed for Chuck Norris API: Public and free!
Best Practicesβ
OpenAPI Spec Creationβ
DO:
- β
Use descriptive
operationId(e.g.,get_random_jokenotendpoint1) - β
Write clear
descriptionfields (LLM reads these to decide tool usage) - β Mark required parameters correctly
- β Include response schemas for better error handling
DON'T:
- β Use generic names like
api_call_1 - β Skip descriptions (LLM won't know when to use tool)
- β Mark all parameters as required (provide sensible defaults)
Tool Designβ
DO:
- β One tool per distinct action (get, search, create, update)
- β Keep parameter lists short (< 5 parameters ideal)
- β Use enums for categorical parameters
- β Test tools independently before agent integration
DON'T:
- β Combine unrelated actions in one endpoint
- β Use overly complex nested parameters
- β Assume LLM will infer missing descriptions
Authenticationβ
Chuck Norris API doesn't need auth, but for APIs that do:
# API Key in header
OpenAPIToolset(
spec=spec,
auth_config={
"type": "api_key",
"api_key": os.getenv("API_KEY"),
"key_name": "X-API-Key",
"key_location": "header"
}
)
# Bearer token
OpenAPIToolset(
spec=spec,
auth_config={
"type": "bearer",
"token": os.getenv("AUTH_TOKEN")
}
)
# OAuth (more complex, see ADK docs)
Common Issues & Troubleshootingβ
Issue 1: Tool Not Being Calledβ
Problem: Agent doesn't use your OpenAPI tool
Solutions:
- Check
operationIdis descriptive:get_random_jokenotendpoint1 - Add detailed
summaryanddescriptionin spec - Test tool directly in Python to verify it works
- Review agent instruction (does it mention the tool's purpose?)
- Check Events tab: Is LLM considering the tool?
Issue 2: Import Errorsβ
Problem: ImportError: cannot import name 'OpenAPIToolset'
Solutions:
- Use correct import path:
from google.adk.tools.openapi_tool import OpenAPIToolset - Verify
google-adkis installed:pip install google-adk - Check ADK version compatibility
Issue 3: Constructor Parameter Errorsβ
Problem: TypeError: OpenAPIToolset.__init__() got an unexpected keyword argument 'spec'
Solutions:
- Use
spec_dictparameter instead ofspec:OpenAPIToolset(spec_dict=my_spec) - Verify the parameter name in your ADK version
Issue 4: Async Tool Loading Issuesβ
Problem: ValidationError: Input should be a valid list [type=list_type, input_value=<coroutine object>]
Solutions:
- Pass the toolset directly:
tools=[my_toolset]nottools=my_toolset.get_tools() get_tools()is async and returns a coroutine - let ADK handle tool loading internally- If you need to access tools directly, await the call:
tools = await my_toolset.get_tools()
Issue 5: Invalid API Responseβ
Problem: Tool returns error or unexpected data
Solutions:
- Test API endpoint directly with
curlor Postman - Verify spec matches actual API behavior
- Check required parameters are being passed
- Look for rate limiting (429 status codes)
- Validate JSON parsing (use try/except in custom wrappers)
Issue 3: Spec Validation Errorsβ
Problem: ADK rejects OpenAPI spec
Solutions:
- Validate spec at https://editor.swagger.io/
- Check OpenAPI version (
3.0.0or3.1.0supported) - Verify all required fields present (
openapi,info,paths) - Use proper JSON types (
stringnotstr,integernotint) - Check for typos in field names
Issue 4: Agent Misinterprets Tool Outputβ
Problem: Agent doesn't properly format API response
Solutions:
- Improve agent instruction to specify output format
- Add examples in instruction: "Extract 'value' field from JSON"
- Use tool result in structured way (dict keys documented)
- Consider post-processing in custom wrapper function
- Check response schema in spec matches actual API
Real-World Applicationsβ
1. GitHub Integrationβ
Use Case: Code review assistant
OpenAPI Tools:
get_pull_request(repo, number)- Get PR detailslist_comments(repo, number)- Get review commentscreate_comment(repo, number, body)- Add review comment
Example: "Summarize the changes in PR #123 and check for security issues"
2. Stripe Payment Processingβ
Use Case: E-commerce support agent
OpenAPI Tools:
create_payment_intent(amount, currency)- Process paymentget_customer(id)- Get customer detailscreate_refund(payment_id, amount)- Issue refund
Example: "Process a refund of $50 for order #456"
3. Twilio SMS/Voiceβ
Use Case: Communication automation agent
OpenAPI Tools:
send_sms(to, body)- Send text messagemake_call(to, from, url)- Initiate phone callget_message_status(sid)- Check delivery status
Example: "Send a confirmation SMS to customer at +1234567890"
4. Jira Project Managementβ
Use Case: Development workflow agent
OpenAPI Tools:
create_issue(project, summary, description)- Create ticketget_issue(key)- Get ticket detailstransition_issue(key, transition_id)- Move to different status
Example: "Create a bug ticket for the login issue and assign it to the backend team"
Advanced Topicsβ
Custom Response Processingβ
Sometimes you need to post-process API responses:
from google.adk.tools import OpenAPIToolset
# Create toolset
toolset = OpenAPIToolset(spec=api_spec)
# Wrap with custom processing
async def search_jokes_enhanced(query: str) -> str:
"""Enhanced search with post-processing"""
result = await toolset.search_jokes(query=query)
# Extract just the jokes
jokes = [item['value'] for item in result.get('result', [])]
# Format nicely
if not jokes:
return f"No jokes found for '{query}'"
return "\n\n".join(f"{i+1}. {joke}" for i, joke in enumerate(jokes[:3]))
# Use enhanced version in agent
root_agent = Agent(
...,
tools=[search_jokes_enhanced] # Use wrapper instead of raw toolset
)
Multiple API Integrationβ
Combine multiple APIs in one agent:
chuck_toolset = OpenAPIToolset(spec=chuck_norris_spec)
github_toolset = OpenAPIToolset(
spec=github_spec,
auth_config={"type": "bearer", "token": github_token}
)
root_agent = Agent(
...,
tools=[chuck_toolset, github_toolset, custom_function]
)
Rate Limiting Handlingβ
Implement retry logic for rate-limited APIs:
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
async def api_call_with_retry():
return await toolset.some_endpoint()
Exercisesβ
- Add Weather API: Integrate OpenWeatherMap API with authentication
- Build News Agent: Use NewsAPI to fetch and summarize articles
- Create Multi-API Agent: Combine 3+ different APIs in one agent
- Custom Wrapper: Write post-processing for Chuck Norris API responses
- Error Handling: Add try/except blocks for network failures
Further Readingβ
- OpenAPI Specification
- Chuck Norris API Documentation
- ADK OpenAPIToolset Documentation
- Swagger Editor - Test OpenAPI specs
- Public APIs List - Find APIs to integrate
Congratulations! You can now connect your agents to any OpenAPI-compliant REST API without writing manual tool code. This opens up integration with thousands of web services!
Next Stepsβ
π Tutorial 04: Sequential Workflows - Learn to orchestrate multiple agents in ordered pipelines
π¬ Join the Discussion
Have questions or feedback? Discuss this tutorial with the community on GitHub Discussions.