Skip to content

Commit 74a3500

Browse files
omarcevicopybara-github
authored andcommitted
fix: #3036 parameter filtering for CrewAI functions with **kwargs
Merge #3037 fix: [#3036](#3036) - Fix FunctionTool parameter filtering to support CrewAI-style tools - Functions with **kwargs now receive all parameters except 'self' and 'tool_context' - Maintains backward compatibility with explicit parameter functions - Add comprehensive tests for **kwargs functionality Fixes parameter filtering issue where CrewAI tools using **kwargs pattern would receive empty parameter dictionaries, causing search_query and other parameters to be None. #non-breaking COPYBARA_INTEGRATE_REVIEW=#3037 from omarcevi:fix/function-tool-kwargs-parameter-filtering 012bbfc PiperOrigin-RevId: 825275686
1 parent 15afbcd commit 74a3500

File tree

7 files changed

+677
-4
lines changed

7 files changed

+677
-4
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# CrewAI Tool **kwargs Parameter Handling
2+
3+
This sample demonstrates how `CrewaiTool` correctly handles tools with
4+
`**kwargs` parameters, which is a common pattern in CrewAI tools.
5+
6+
## What This Sample Demonstrates
7+
8+
### Key Feature: **kwargs Parameter Passing
9+
10+
CrewAI tools often accept arbitrary parameters via `**kwargs`:
11+
12+
```python
13+
def _run(self, query: str, **kwargs) -> str:
14+
# Extra parameters are passed through kwargs
15+
category = kwargs.get('category')
16+
date_range = kwargs.get('date_range')
17+
limit = kwargs.get('limit')
18+
```
19+
20+
The `CrewaiTool` wrapper detects this pattern and passes all parameters through
21+
(except framework-managed ones like `self` and `tool_context`).
22+
23+
### Contrast with Regular Tools
24+
25+
For comparison, tools without `**kwargs` only accept explicitly declared
26+
parameters:
27+
28+
```python
29+
def _run(self, query: str, category: str) -> str:
30+
```
31+
32+
## Prerequisites
33+
34+
### Required: CrewAI Tools (Python 3.10+)
35+
36+
```bash
37+
pip install 'crewai-tools>=0.2.0'
38+
```
39+
40+
### Required: API Key
41+
42+
```bash
43+
export GOOGLE_API_KEY="your-api-key-here"
44+
# OR
45+
export GOOGLE_GENAI_API_KEY="your-api-key-here"
46+
```
47+
48+
## Running the Sample
49+
50+
### Option 1: Run the Happy Path Test
51+
52+
```bash
53+
cd contributing/samples/crewai_tool_kwargs
54+
python main.py
55+
```
56+
57+
**Expected output:**
58+
```
59+
============================================================
60+
CrewAI Tool **kwargs Parameter Test
61+
============================================================
62+
63+
🧪 Test 1: Basic search (no extra parameters)
64+
User: Search for Python tutorials
65+
Agent: [Uses tool and returns results]
66+
67+
🧪 Test 2: Search with filters (**kwargs test)
68+
User: Search for machine learning articles, filtered by...
69+
Agent: [Uses tool with category, date_range, and limit parameters]
70+
71+
============================================================
72+
✅ Happy path test completed successfully!
73+
============================================================
74+
```
75+
76+
## What Gets Tested
77+
78+
**CrewAI tool integration** - Wrapping a CrewAI BaseTool with ADK
79+
**Basic parameters** - Required `query` parameter passes correctly
80+
****kwargs passing** - Extra parameters (category, date_range, limit) pass
81+
through
82+
**End-to-end execution** - Tool executes and returns results to agent
83+
84+
## Code Structure
85+
86+
```
87+
crewai_tool_kwargs/
88+
├── __init__.py # Module initialization
89+
├── agent.py # Agent with CrewAI tool
90+
├── main.py # Happy path test
91+
└── README.md # This file
92+
```
93+
94+
### Key Files
95+
96+
**agent.py:**
97+
98+
- Defines `CustomSearchTool` (CrewAI BaseTool with **kwargs)
99+
- Wraps it with `CrewaiTool`
100+
- Creates agent with the wrapped tool
101+
102+
**main.py:**
103+
104+
- Test 1: Basic search (no extra params)
105+
- Test 2: Search with filters (tests **kwargs)
106+
107+
## How It Works
108+
109+
1. **CrewAI Tool Definition** (`agent.py`):
110+
```python
111+
class CustomSearchTool(BaseTool):
112+
def _run(self, query: str, **kwargs) -> str:
113+
# kwargs receives: category, date_range, limit, etc.
114+
```
115+
116+
2. **ADK Wrapping** (`agent.py`):
117+
```python
118+
adk_search_tool = CrewaiTool(
119+
crewai_search_tool,
120+
name="search_with_filters",
121+
description="..."
122+
)
123+
```
124+
125+
3. **LLM Function Calling** (`main.py`):
126+
- LLM sees the tool in function calling format
127+
- LLM calls with: `{query: "...", category: "...", date_range: "...", limit: 10}`
128+
- CrewaiTool passes ALL parameters to `**kwargs`
129+
130+
4. **Tool Execution**:
131+
- `query` → positional parameter
132+
- `category`, `date_range`, `limit` → collected in `**kwargs`
133+
- Tool logic uses all parameters
134+
135+
## Troubleshooting
136+
137+
### ImportError: No module named 'crewai'
138+
139+
```bash
140+
pip install 'crewai-tools>=0.2.0'
141+
```
142+
143+
### Python Version Error
144+
145+
CrewAI requires Python 3.10+:
146+
147+
```bash
148+
python --version # Should be 3.10 or higher
149+
```
150+
151+
### Missing API Key
152+
153+
```bash
154+
export GOOGLE_API_KEY="your-key-here"
155+
```
156+
157+
## Related
158+
159+
- Parent class: `FunctionTool` - Base class for all function-based tools
160+
- Unit tests: `tests/unittests/tools/test_crewai_tool.py`
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from . import agent
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Sample demonstrating CrewAI tool with **kwargs parameter handling.
16+
17+
This sample shows how CrewaiTool correctly passes arbitrary parameters
18+
through **kwargs, which is a common pattern in CrewAI tools.
19+
"""
20+
21+
from typing import Optional
22+
23+
from crewai.tools import BaseTool
24+
from google.adk import Agent
25+
from google.adk.tools.crewai_tool import CrewaiTool
26+
from pydantic import BaseModel
27+
from pydantic import Field
28+
29+
30+
class SearchInput(BaseModel):
31+
"""Input schema for the search tool."""
32+
33+
query: str = Field(..., description="The search query string")
34+
category: Optional[str] = Field(
35+
None, description="Filter by category (e.g., 'technology', 'science')"
36+
)
37+
date_range: Optional[str] = Field(
38+
None, description="Filter by date range (e.g., 'last_week', '2024')"
39+
)
40+
limit: Optional[int] = Field(
41+
None, description="Limit the number of results (e.g., 10, 20)"
42+
)
43+
44+
45+
class CustomSearchTool(BaseTool):
46+
"""A custom CrewAI tool that accepts arbitrary search parameters via **kwargs.
47+
48+
This demonstrates the key CrewAI tool pattern where tools accept
49+
flexible parameters through **kwargs.
50+
"""
51+
52+
name: str = "custom_search"
53+
description: str = (
54+
"Search for information with flexible filtering options. "
55+
"Accepts a query and optional filter parameters like category, "
56+
"date_range, limit, etc."
57+
)
58+
args_schema: type[BaseModel] = SearchInput
59+
60+
def _run(self, query: str, **kwargs) -> str:
61+
"""Execute search with arbitrary filter parameters.
62+
63+
Args:
64+
query: The search query string.
65+
**kwargs: Additional filter parameters like category, date_range, limit.
66+
67+
Returns:
68+
A formatted string showing the query and applied filters.
69+
"""
70+
result_parts = [f"Searching for: '{query}'"]
71+
72+
if kwargs:
73+
result_parts.append("Applied filters:")
74+
for key, value in kwargs.items():
75+
result_parts.append(f" - {key}: {value}")
76+
else:
77+
result_parts.append("No additional filters applied.")
78+
79+
# Simulate search results
80+
result_parts.append(f"\nFound 3 results matching your criteria.")
81+
82+
return "\n".join(result_parts)
83+
84+
85+
crewai_search_tool = CustomSearchTool()
86+
87+
# Wrap it with ADK's CrewaiTool
88+
adk_search_tool = CrewaiTool(
89+
crewai_search_tool,
90+
name="search_with_filters",
91+
description=(
92+
"Search for information with optional filters like category, "
93+
"date_range, or limit"
94+
),
95+
)
96+
97+
root_agent = Agent(
98+
model="gemini-2.0-flash",
99+
name="search_agent",
100+
description="An agent that can search with flexible filtering options",
101+
instruction="""
102+
You are a helpful search assistant.
103+
When users ask you to search, use the search_with_filters tool.
104+
You can pass additional parameters like:
105+
- category: to filter by category (e.g., "technology", "science")
106+
- date_range: to filter by date (e.g., "last_week", "2024")
107+
- limit: to limit the number of results (e.g., 10, 20)
108+
109+
Always acknowledge what filters you're applying.
110+
""",
111+
tools=[adk_search_tool],
112+
)
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Happy path test for CrewAI tool with **kwargs parameter handling.
16+
17+
This demonstrates that CrewaiTool correctly passes arbitrary parameters
18+
through **kwargs to the underlying CrewAI tool.
19+
"""
20+
21+
import asyncio
22+
23+
import agent
24+
from dotenv import load_dotenv
25+
from google.adk.cli.utils import logs
26+
from google.adk.runners import InMemoryRunner
27+
from google.genai import types
28+
29+
load_dotenv(override=True)
30+
logs.log_to_tmp_folder()
31+
32+
33+
async def main():
34+
"""Run happy path test demonstrating **kwargs parameter passing."""
35+
app_name = "crewai_kwargs_test"
36+
user_id = "test_user"
37+
38+
runner = InMemoryRunner(
39+
agent=agent.root_agent,
40+
app_name=app_name,
41+
)
42+
43+
session = await runner.session_service.create_session(
44+
app_name=app_name, user_id=user_id
45+
)
46+
47+
print("=" * 60)
48+
print("CrewAI Tool **kwargs Parameter Test")
49+
print("=" * 60)
50+
51+
# Test 1: Simple search without extra parameters
52+
print("\n🧪 Test 1: Basic search (no extra parameters)")
53+
print("-" * 60)
54+
content1 = types.Content(
55+
role="user",
56+
parts=[types.Part.from_text(text="Search for Python tutorials")],
57+
)
58+
print(f"User: {content1.parts[0].text}")
59+
60+
async for event in runner.run_async(
61+
user_id=user_id,
62+
session_id=session.id,
63+
new_message=content1,
64+
):
65+
if event.content.parts and event.content.parts[0].text:
66+
print(f"Agent: {event.content.parts[0].text}")
67+
68+
# Test 2: Search with extra parameters (testing **kwargs)
69+
print("\n🧪 Test 2: Search with filters (**kwargs test)")
70+
print("-" * 60)
71+
content2 = types.Content(
72+
role="user",
73+
parts=[
74+
types.Part.from_text(
75+
text=(
76+
"Search for machine learning articles, filtered by category"
77+
" 'technology', date_range 'last_month', and limit to 10"
78+
" results"
79+
)
80+
)
81+
],
82+
)
83+
print(f"User: {content2.parts[0].text}")
84+
85+
async for event in runner.run_async(
86+
user_id=user_id,
87+
session_id=session.id,
88+
new_message=content2,
89+
):
90+
if event.content.parts and event.content.parts[0].text:
91+
print(f"Agent: {event.content.parts[0].text}")
92+
93+
# Verify success
94+
print("\n" + "=" * 60)
95+
print("✅ Happy path test completed successfully!")
96+
print("=" * 60)
97+
print("\nVerified behaviors:")
98+
print(" ✅ CrewAI tool integrated with ADK agent")
99+
print(" ✅ Basic parameters passed correctly")
100+
print(" ✅ Extra parameters passed through **kwargs")
101+
print(" ✅ Tool executed and returned results")
102+
103+
104+
if __name__ == "__main__":
105+
asyncio.run(main())

0 commit comments

Comments
 (0)