Skip to content

Commit b24f604

Browse files
wukathcopybara-github
authored andcommitted
chore: Move langchain tool into integrations folder
Co-authored-by: Kathy Wu <wukathy@google.com> PiperOrigin-RevId: 883403479
1 parent e3b58e2 commit b24f604

File tree

4 files changed

+216
-165
lines changed

4 files changed

+216
-165
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 .langchain_tool import LangchainTool
14+
from .langchain_tool import LangchainToolConfig
15+
16+
__all__ = [
17+
'LangchainTool',
18+
'LangchainToolConfig',
19+
]
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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+
from typing import Optional
18+
from typing import Union
19+
20+
from google.genai import types
21+
from langchain_core.tools import BaseTool as LangchainBaseTool
22+
from langchain_core.tools import Tool
23+
from langchain_core.tools.structured import StructuredTool
24+
from typing_extensions import override
25+
26+
from ...tools import _automatic_function_calling_util
27+
from ...tools.function_tool import FunctionTool
28+
from ...tools.tool_configs import BaseToolConfig
29+
from ...tools.tool_configs import ToolArgsConfig
30+
31+
32+
class LangchainTool(FunctionTool):
33+
"""Adapter class that wraps a Langchain tool for use with ADK.
34+
35+
This adapter converts Langchain tools into a format compatible with Google's
36+
generative AI function calling interface. It preserves the tool's name,
37+
description, and functionality while adapting its schema.
38+
39+
The original tool's name and description can be overridden if needed.
40+
41+
Args:
42+
tool: A Langchain tool to wrap (BaseTool or a tool with a .run method)
43+
name: Optional override for the tool's name
44+
description: Optional override for the tool's description
45+
46+
Examples::
47+
48+
from langchain.tools import DuckDuckGoSearchTool
49+
from google.adk.integrations.langchain import LangchainTool
50+
51+
search_tool = DuckDuckGoSearchTool()
52+
wrapped_tool = LangchainTool(search_tool)
53+
"""
54+
55+
_langchain_tool: Union[LangchainBaseTool, object]
56+
"""The wrapped langchain tool."""
57+
58+
def __init__(
59+
self,
60+
tool: Union[LangchainBaseTool, object],
61+
name: Optional[str] = None,
62+
description: Optional[str] = None,
63+
):
64+
if not hasattr(tool, 'run') and not hasattr(tool, '_run'):
65+
raise ValueError(
66+
"Tool must be a Langchain tool, have a 'run' or '_run' method."
67+
)
68+
69+
# Determine which function to use
70+
if isinstance(tool, StructuredTool):
71+
func = tool.func
72+
# For async tools, func might be None but coroutine exists
73+
if func is None and hasattr(tool, 'coroutine') and tool.coroutine:
74+
func = tool.coroutine
75+
elif hasattr(tool, '_run') or hasattr(tool, 'run'):
76+
func = tool._run if hasattr(tool, '_run') else tool.run
77+
else:
78+
raise ValueError(
79+
"This is not supported. Tool must be a Langchain tool, have a 'run'"
80+
" or '_run' method. The tool is: ",
81+
type(tool),
82+
)
83+
84+
super().__init__(func)
85+
# run_manager is a special parameter for langchain tool
86+
self._ignore_params.append('run_manager')
87+
self._langchain_tool = tool
88+
89+
# Set name: priority is 1) explicitly provided name, 2) tool's name, 3) default
90+
if name is not None:
91+
self.name = name
92+
elif hasattr(tool, 'name') and tool.name:
93+
self.name = tool.name
94+
# else: keep default from FunctionTool
95+
96+
# Set description: similar priority
97+
if description is not None:
98+
self.description = description
99+
elif hasattr(tool, 'description') and tool.description:
100+
self.description = tool.description
101+
# else: keep default from FunctionTool
102+
103+
@override
104+
def _get_declaration(self) -> types.FunctionDeclaration:
105+
"""Build the function declaration for the tool.
106+
107+
Returns:
108+
A FunctionDeclaration object that describes the tool's interface.
109+
110+
Raises:
111+
ValueError: If the tool schema cannot be correctly parsed.
112+
"""
113+
try:
114+
# There are two types of tools:
115+
# 1. BaseTool: the tool is defined in langchain_core.tools.
116+
# 2. Other tools: the tool doesn't inherit any class but follow some
117+
# conventions, like having a "run" method.
118+
# Handle BaseTool type (preferred Langchain approach)
119+
if isinstance(self._langchain_tool, LangchainBaseTool):
120+
tool_wrapper = Tool(
121+
name=self.name,
122+
func=self.func,
123+
description=self.description,
124+
)
125+
126+
# Add schema if available
127+
if (
128+
hasattr(self._langchain_tool, 'args_schema')
129+
and self._langchain_tool.args_schema
130+
):
131+
tool_wrapper.args_schema = self._langchain_tool.args_schema
132+
133+
return _automatic_function_calling_util.build_function_declaration_for_langchain(
134+
False,
135+
self.name,
136+
self.description,
137+
tool_wrapper.func,
138+
tool_wrapper.args,
139+
)
140+
141+
# Need to provide a way to override the function names and descriptions
142+
# as the original function names are mostly ".run" and the descriptions
143+
# may not meet users' needs
144+
function_decl = super()._get_declaration()
145+
function_decl.name = self.name
146+
function_decl.description = self.description
147+
return function_decl
148+
149+
except Exception as e:
150+
raise ValueError(
151+
f'Failed to build function declaration for Langchain tool: {e}'
152+
) from e
153+
154+
@override
155+
@classmethod
156+
def from_config(
157+
cls: type[LangchainTool], config: ToolArgsConfig, config_abs_path: str
158+
) -> LangchainTool:
159+
from ...agents import config_agent_utils
160+
161+
langchain_tool_config = LangchainToolConfig.model_validate(
162+
config.model_dump()
163+
)
164+
tool = config_agent_utils.resolve_fully_qualified_name(
165+
langchain_tool_config.tool
166+
)
167+
name = langchain_tool_config.name
168+
description = langchain_tool_config.description
169+
return cls(tool, name=name, description=description)
170+
171+
172+
class LangchainToolConfig(BaseToolConfig):
173+
tool: str
174+
"""The fully qualified path of the Langchain tool instance."""
175+
176+
name: str = ''
177+
"""The name of the tool."""
178+
179+
description: str = ''
180+
"""The description of the tool."""

src/google/adk/tools/langchain_tool.py

Lines changed: 16 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -14,167 +14,19 @@
1414

1515
from __future__ import annotations
1616

17-
from typing import Optional
18-
from typing import Union
19-
20-
from google.genai import types
21-
from langchain_core.tools import BaseTool as LangchainBaseTool
22-
from langchain_core.tools import Tool
23-
from langchain_core.tools.structured import StructuredTool
24-
from typing_extensions import override
25-
26-
from . import _automatic_function_calling_util
27-
from .function_tool import FunctionTool
28-
from .tool_configs import BaseToolConfig
29-
from .tool_configs import ToolArgsConfig
30-
31-
32-
class LangchainTool(FunctionTool):
33-
"""Adapter class that wraps a Langchain tool for use with ADK.
34-
35-
This adapter converts Langchain tools into a format compatible with Google's
36-
generative AI function calling interface. It preserves the tool's name,
37-
description, and functionality while adapting its schema.
38-
39-
The original tool's name and description can be overridden if needed.
40-
41-
Args:
42-
tool: A Langchain tool to wrap (BaseTool or a tool with a .run method)
43-
name: Optional override for the tool's name
44-
description: Optional override for the tool's description
45-
46-
Examples::
47-
48-
from langchain.tools import DuckDuckGoSearchTool
49-
from google.genai.tools import LangchainTool
50-
51-
search_tool = DuckDuckGoSearchTool()
52-
wrapped_tool = LangchainTool(search_tool)
53-
"""
54-
55-
_langchain_tool: Union[LangchainBaseTool, object]
56-
"""The wrapped langchain tool."""
57-
58-
def __init__(
59-
self,
60-
tool: Union[LangchainBaseTool, object],
61-
name: Optional[str] = None,
62-
description: Optional[str] = None,
63-
):
64-
if not hasattr(tool, 'run') and not hasattr(tool, '_run'):
65-
raise ValueError(
66-
"Tool must be a Langchain tool, have a 'run' or '_run' method."
67-
)
68-
69-
# Determine which function to use
70-
if isinstance(tool, StructuredTool):
71-
func = tool.func
72-
# For async tools, func might be None but coroutine exists
73-
if func is None and hasattr(tool, 'coroutine') and tool.coroutine:
74-
func = tool.coroutine
75-
elif hasattr(tool, '_run') or hasattr(tool, 'run'):
76-
func = tool._run if hasattr(tool, '_run') else tool.run
77-
else:
78-
raise ValueError(
79-
"This is not supported. Tool must be a Langchain tool, have a 'run'"
80-
" or '_run' method. The tool is: ",
81-
type(tool),
82-
)
83-
84-
super().__init__(func)
85-
# run_manager is a special parameter for langchain tool
86-
self._ignore_params.append('run_manager')
87-
self._langchain_tool = tool
88-
89-
# Set name: priority is 1) explicitly provided name, 2) tool's name, 3) default
90-
if name is not None:
91-
self.name = name
92-
elif hasattr(tool, 'name') and tool.name:
93-
self.name = tool.name
94-
# else: keep default from FunctionTool
95-
96-
# Set description: similar priority
97-
if description is not None:
98-
self.description = description
99-
elif hasattr(tool, 'description') and tool.description:
100-
self.description = tool.description
101-
# else: keep default from FunctionTool
102-
103-
@override
104-
def _get_declaration(self) -> types.FunctionDeclaration:
105-
"""Build the function declaration for the tool.
106-
107-
Returns:
108-
A FunctionDeclaration object that describes the tool's interface.
109-
110-
Raises:
111-
ValueError: If the tool schema cannot be correctly parsed.
112-
"""
113-
try:
114-
# There are two types of tools:
115-
# 1. BaseTool: the tool is defined in langchain_core.tools.
116-
# 2. Other tools: the tool doesn't inherit any class but follow some
117-
# conventions, like having a "run" method.
118-
# Handle BaseTool type (preferred Langchain approach)
119-
if isinstance(self._langchain_tool, LangchainBaseTool):
120-
tool_wrapper = Tool(
121-
name=self.name,
122-
func=self.func,
123-
description=self.description,
124-
)
125-
126-
# Add schema if available
127-
if (
128-
hasattr(self._langchain_tool, 'args_schema')
129-
and self._langchain_tool.args_schema
130-
):
131-
tool_wrapper.args_schema = self._langchain_tool.args_schema
132-
133-
return _automatic_function_calling_util.build_function_declaration_for_langchain(
134-
False,
135-
self.name,
136-
self.description,
137-
tool_wrapper.func,
138-
tool_wrapper.args,
139-
)
140-
141-
# Need to provide a way to override the function names and descriptions
142-
# as the original function names are mostly ".run" and the descriptions
143-
# may not meet users' needs
144-
function_decl = super()._get_declaration()
145-
function_decl.name = self.name
146-
function_decl.description = self.description
147-
return function_decl
148-
149-
except Exception as e:
150-
raise ValueError(
151-
f'Failed to build function declaration for Langchain tool: {e}'
152-
) from e
153-
154-
@override
155-
@classmethod
156-
def from_config(
157-
cls: type[LangchainTool], config: ToolArgsConfig, config_abs_path: str
158-
) -> LangchainTool:
159-
from ..agents import config_agent_utils
160-
161-
langchain_tool_config = LangchainToolConfig.model_validate(
162-
config.model_dump()
163-
)
164-
tool = config_agent_utils.resolve_fully_qualified_name(
165-
langchain_tool_config.tool
166-
)
167-
name = langchain_tool_config.name
168-
description = langchain_tool_config.description
169-
return cls(tool, name=name, description=description)
170-
171-
172-
class LangchainToolConfig(BaseToolConfig):
173-
tool: str
174-
"""The fully qualified path of the Langchain tool instance."""
175-
176-
name: str = ''
177-
"""The name of the tool."""
178-
179-
description: str = ''
180-
"""The description of the tool."""
17+
import warnings
18+
19+
from google.adk.integrations.langchain import LangchainTool
20+
from google.adk.integrations.langchain import LangchainToolConfig
21+
22+
warnings.warn(
23+
"google.adk.tools.langchain_tool is moved to"
24+
" google.adk.integrations.langchain",
25+
DeprecationWarning,
26+
stacklevel=2,
27+
)
28+
29+
__all__ = [
30+
"LangchainTool",
31+
"LangchainToolConfig",
32+
]

tests/unittests/tools/test_langchain_tool.py renamed to tests/unittests/integrations/langchain/test_langchain_tool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from unittest.mock import MagicMock
1616

17-
from google.adk.tools.langchain_tool import LangchainTool
17+
from google.adk.integrations.langchain import LangchainTool
1818
from langchain_core.tools import tool
1919
from langchain_core.tools.structured import StructuredTool
2020
from pydantic import BaseModel

0 commit comments

Comments
 (0)