hi @eric-burel
you can check what’s the runtime type of the tool. It should show something like this:
from langchain_core.tools import tool
from langchain_core.tools.structured import StructuredTool
from langchain_core.tools.simple import Tool
@tool
def echo(foo: str) -> str:
"""Echo."""
return foo
type(echo).__name__ # 'StructuredTool'
isinstance(echo, StructuredTool) # True
@tool(infer_schema=False)
def echo2(foo: str) -> str:
"""Echo."""
return foo
type(echo2).__name__ # 'Tool'
isinstance(echo2, Tool) # True
Both StructuredTool and Tool define .func - but their common parent BaseTool does not.
Some workarounds:
1. StructuredTool.from_function()
from langchain_core.tools import StructuredTool
def echo(foo: str) -> str:
"""Echo the input."""
return foo
echo_tool = StructuredTool.from_function(echo)
# Type checker knows echo_tool is StructuredTool → .func is valid
echo_tool.func("bar") # typed! autocomplete works
For testing, you get the best of both worlds:
# Unit test - call .func with full type safety
def test_echo_logic():
assert echo_tool.func("hello") == "hello"
# Integration test - exercise validation, callbacks, the full pipeline
def test_echo_pipeline():
assert echo_tool.invoke({"foo": "hello"}) == "hello"
2. isinstance type narrowing in tests
from langchain_core.tools import tool, StructuredTool
@tool
def echo(foo: str) -> str:
"""Echo the input."""
return foo
# In your test:
def test_echo():
assert isinstance(echo, StructuredTool) # narrows type for the checker
# After isinstance, type checker knows echo is StructuredTool
result = echo.func("bar") # now typed! no error
assert result == "bar"
The isinstance check is also a useful runtime guard: if LangChain ever changes what @tool returns, your test fails immediately with a clear message instead of a cryptic AttributeError.
3. cast
from typing import cast
from langchain_core.tools import tool, StructuredTool
@tool
def echo(foo: str) -> str:
"""Echo the input."""
return foo
echo_typed = cast(StructuredTool, echo)
echo_typed.func("bar") # typed
This is a type-only assertion, no runtime cost - but it doesn’t verify anything at runtime like isinstance does.