Skip to content

Commit e3b58e2

Browse files
wukathcopybara-github
authored andcommitted
chore: Move crew ai tool into integrations folder
Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 883401885
1 parent 8f82697 commit e3b58e2

File tree

4 files changed

+191
-141
lines changed

4 files changed

+191
-141
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
from .crewai_tool import CrewaiTool
14+
from .crewai_tool import CrewaiToolConfig
15+
16+
__all__ = [
17+
'CrewaiTool',
18+
'CrewaiToolConfig',
19+
]
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
# Copyright 2026 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 __future__ import annotations
16+
17+
import inspect
18+
from typing import Any
19+
from typing import Callable
20+
21+
from google.genai import types
22+
from typing_extensions import override
23+
24+
from ...tools import _automatic_function_calling_util
25+
from ...tools.function_tool import FunctionTool
26+
from ...tools.tool_configs import BaseToolConfig
27+
from ...tools.tool_configs import ToolArgsConfig
28+
from ...tools.tool_context import ToolContext
29+
30+
try:
31+
from crewai.tools import BaseTool as CrewaiBaseTool
32+
except ImportError as e:
33+
raise ImportError(
34+
"Crewai Tools require pip install 'google-adk[extensions]'."
35+
) from e
36+
37+
38+
class CrewaiTool(FunctionTool):
39+
"""Use this class to wrap a CrewAI tool.
40+
41+
If the original tool name and description are not suitable, you can override
42+
them in the constructor.
43+
"""
44+
45+
tool: CrewaiBaseTool
46+
"""The wrapped CrewAI tool."""
47+
48+
def __init__(self, tool: CrewaiBaseTool, *, name: str, description: str = ''):
49+
super().__init__(tool.run)
50+
self.tool = tool
51+
if name:
52+
self.name = name
53+
elif tool.name:
54+
# Right now, CrewAI tool name contains white spaces. White spaces are
55+
# not supported in our framework. So we replace them with "_".
56+
self.name = tool.name.replace(' ', '_').lower()
57+
if description:
58+
self.description = description
59+
elif tool.description:
60+
self.description = tool.description
61+
62+
@override
63+
async def run_async(
64+
self, *, args: dict[str, Any], tool_context: ToolContext
65+
) -> Any:
66+
"""Override run_async to handle CrewAI-specific parameter filtering.
67+
68+
CrewAI tools use **kwargs pattern, so we need special parameter filtering
69+
logic that allows all parameters to pass through while removing only
70+
reserved parameters like 'self' and 'tool_context'.
71+
72+
Note: 'tool_context' is removed from the initial args dictionary to prevent
73+
duplicates, but is re-added if the function signature explicitly requires it
74+
as a parameter.
75+
"""
76+
# Preprocess arguments (includes Pydantic model conversion)
77+
args_to_call = self._preprocess_args(args)
78+
79+
signature = inspect.signature(self.func)
80+
valid_params = {param for param in signature.parameters}
81+
82+
# Check if function accepts **kwargs
83+
has_kwargs = any(
84+
param.kind == inspect.Parameter.VAR_KEYWORD
85+
for param in signature.parameters.values()
86+
)
87+
88+
if has_kwargs:
89+
# For functions with **kwargs, we pass all arguments. We defensively
90+
# remove arguments like `self` that are managed by the framework and not
91+
# intended to be passed through **kwargs.
92+
args_to_call.pop('self', None)
93+
# We also remove context param that might have been passed in `args`,
94+
# as it will be explicitly injected later if it's a valid parameter.
95+
args_to_call.pop(self._context_param_name, None)
96+
else:
97+
# For functions without **kwargs, use the original filtering.
98+
args_to_call = {
99+
k: v for k, v in args_to_call.items() if k in valid_params
100+
}
101+
102+
# Inject context if it's an explicit parameter. This will add it
103+
# or overwrite any value that might have been passed in `args`.
104+
if self._context_param_name in valid_params:
105+
args_to_call[self._context_param_name] = tool_context
106+
107+
# Check for missing mandatory arguments
108+
mandatory_args = self._get_mandatory_args()
109+
missing_mandatory_args = [
110+
arg for arg in mandatory_args if arg not in args_to_call
111+
]
112+
113+
if missing_mandatory_args:
114+
missing_mandatory_args_str = '\n'.join(missing_mandatory_args)
115+
error_str = f"""Invoking `{self.name}()` failed as the following mandatory input parameters are not present:
116+
{missing_mandatory_args_str}
117+
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
118+
return {'error': error_str}
119+
120+
return await self._invoke_callable(self.func, args_to_call)
121+
122+
@override
123+
def _get_declaration(self) -> types.FunctionDeclaration:
124+
"""Build the function declaration for the tool."""
125+
function_declaration = _automatic_function_calling_util.build_function_declaration_for_params_for_crewai(
126+
False,
127+
self.name,
128+
self.description,
129+
self.func,
130+
self.tool.args_schema.model_json_schema(),
131+
)
132+
return function_declaration
133+
134+
@override
135+
@classmethod
136+
def from_config(
137+
cls: type[CrewaiTool], config: ToolArgsConfig, config_abs_path: str
138+
) -> CrewaiTool:
139+
from ...agents import config_agent_utils
140+
141+
crewai_tool_config = CrewaiToolConfig.model_validate(config.model_dump())
142+
tool = config_agent_utils.resolve_fully_qualified_name(
143+
crewai_tool_config.tool
144+
)
145+
name = crewai_tool_config.name
146+
description = crewai_tool_config.description
147+
return cls(tool, name=name, description=description)
148+
149+
150+
class CrewaiToolConfig(BaseToolConfig):
151+
tool: str
152+
"""The fully qualified path of the CrewAI tool instance."""
153+
154+
name: str = ''
155+
"""The name of the tool."""
156+
157+
description: str = ''
158+
"""The description of the tool."""

src/google/adk/tools/crewai_tool.py

Lines changed: 12 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -14,145 +14,18 @@
1414

1515
from __future__ import annotations
1616

17-
import inspect
18-
from typing import Any
19-
from typing import Callable
17+
import warnings
2018

21-
from google.genai import types
22-
from typing_extensions import override
19+
from google.adk.integrations.crewai import CrewaiTool
20+
from google.adk.integrations.crewai import CrewaiToolConfig
2321

24-
from . import _automatic_function_calling_util
25-
from .function_tool import FunctionTool
26-
from .tool_configs import BaseToolConfig
27-
from .tool_configs import ToolArgsConfig
28-
from .tool_context import ToolContext
22+
warnings.warn(
23+
"google.adk.tools.crewai_tool is moved to google.adk.integrations.crewai",
24+
DeprecationWarning,
25+
stacklevel=2,
26+
)
2927

30-
try:
31-
from crewai.tools import BaseTool as CrewaiBaseTool
32-
except ImportError as e:
33-
raise ImportError(
34-
"Crewai Tools require pip install 'google-adk[extensions]'."
35-
) from e
36-
37-
38-
class CrewaiTool(FunctionTool):
39-
"""Use this class to wrap a CrewAI tool.
40-
41-
If the original tool name and description are not suitable, you can override
42-
them in the constructor.
43-
"""
44-
45-
tool: CrewaiBaseTool
46-
"""The wrapped CrewAI tool."""
47-
48-
def __init__(self, tool: CrewaiBaseTool, *, name: str, description: str):
49-
super().__init__(tool.run)
50-
self.tool = tool
51-
if name:
52-
self.name = name
53-
elif tool.name:
54-
# Right now, CrewAI tool name contains white spaces. White spaces are
55-
# not supported in our framework. So we replace them with "_".
56-
self.name = tool.name.replace(' ', '_').lower()
57-
if description:
58-
self.description = description
59-
elif tool.description:
60-
self.description = tool.description
61-
62-
@override
63-
async def run_async(
64-
self, *, args: dict[str, Any], tool_context: ToolContext
65-
) -> Any:
66-
"""Override run_async to handle CrewAI-specific parameter filtering.
67-
68-
CrewAI tools use **kwargs pattern, so we need special parameter filtering
69-
logic that allows all parameters to pass through while removing only
70-
reserved parameters like 'self' and 'tool_context'.
71-
72-
Note: 'tool_context' is removed from the initial args dictionary to prevent
73-
duplicates, but is re-added if the function signature explicitly requires it
74-
as a parameter.
75-
"""
76-
# Preprocess arguments (includes Pydantic model conversion)
77-
args_to_call = self._preprocess_args(args)
78-
79-
signature = inspect.signature(self.func)
80-
valid_params = {param for param in signature.parameters}
81-
82-
# Check if function accepts **kwargs
83-
has_kwargs = any(
84-
param.kind == inspect.Parameter.VAR_KEYWORD
85-
for param in signature.parameters.values()
86-
)
87-
88-
if has_kwargs:
89-
# For functions with **kwargs, we pass all arguments. We defensively
90-
# remove arguments like `self` that are managed by the framework and not
91-
# intended to be passed through **kwargs.
92-
args_to_call.pop('self', None)
93-
# We also remove context param that might have been passed in `args`,
94-
# as it will be explicitly injected later if it's a valid parameter.
95-
args_to_call.pop(self._context_param_name, None)
96-
else:
97-
# For functions without **kwargs, use the original filtering.
98-
args_to_call = {
99-
k: v for k, v in args_to_call.items() if k in valid_params
100-
}
101-
102-
# Inject context if it's an explicit parameter. This will add it
103-
# or overwrite any value that might have been passed in `args`.
104-
if self._context_param_name in valid_params:
105-
args_to_call[self._context_param_name] = tool_context
106-
107-
# Check for missing mandatory arguments
108-
mandatory_args = self._get_mandatory_args()
109-
missing_mandatory_args = [
110-
arg for arg in mandatory_args if arg not in args_to_call
111-
]
112-
113-
if missing_mandatory_args:
114-
missing_mandatory_args_str = '\n'.join(missing_mandatory_args)
115-
error_str = f"""Invoking `{self.name}()` failed as the following mandatory input parameters are not present:
116-
{missing_mandatory_args_str}
117-
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
118-
return {'error': error_str}
119-
120-
return await self._invoke_callable(self.func, args_to_call)
121-
122-
@override
123-
def _get_declaration(self) -> types.FunctionDeclaration:
124-
"""Build the function declaration for the tool."""
125-
function_declaration = _automatic_function_calling_util.build_function_declaration_for_params_for_crewai(
126-
False,
127-
self.name,
128-
self.description,
129-
self.func,
130-
self.tool.args_schema.model_json_schema(),
131-
)
132-
return function_declaration
133-
134-
@override
135-
@classmethod
136-
def from_config(
137-
cls: type[CrewaiTool], config: ToolArgsConfig, config_abs_path: str
138-
) -> CrewaiTool:
139-
from ..agents import config_agent_utils
140-
141-
crewai_tool_config = CrewaiToolConfig.model_validate(config.model_dump())
142-
tool = config_agent_utils.resolve_fully_qualified_name(
143-
crewai_tool_config.tool
144-
)
145-
name = crewai_tool_config.name
146-
description = crewai_tool_config.description
147-
return cls(tool, name=name, description=description)
148-
149-
150-
class CrewaiToolConfig(BaseToolConfig):
151-
tool: str
152-
"""The fully qualified path of the CrewAI tool instance."""
153-
154-
name: str = ''
155-
"""The name of the tool."""
156-
157-
description: str = ''
158-
"""The description of the tool."""
28+
__all__ = [
29+
"CrewaiTool",
30+
"CrewaiToolConfig",
31+
]

tests/unittests/tools/test_crewai_tool.py renamed to tests/unittests/integrations/crewai/test_crewai_tool.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@
1818

1919
# Skip entire module if Python < 3.10 (must be before crewai_tool import)
2020
pytest.importorskip(
21-
"google.adk.tools.crewai_tool", reason="Requires Python 3.10+"
21+
"google.adk.integrations.crewai.crewai_tool", reason="Requires Python 3.10+"
2222
)
2323

2424
from google.adk.agents.context import Context
2525
from google.adk.agents.invocation_context import InvocationContext
26+
from google.adk.integrations.crewai import CrewaiTool
2627
from google.adk.sessions.session import Session
27-
from google.adk.tools.crewai_tool import CrewaiTool
2828
from google.adk.tools.tool_context import ToolContext
2929

3030

0 commit comments

Comments
 (0)