hi @UK59
that is a very interesting question. Let me explain my understanding of your question.
General overview:
Q1 Recommended architecture/patterns
-
Package tools in separate modules/packages: Keep each tool independently installable (e.g., external_tools_repo) exposing either:
- Plain functions with type hints and clear docstrings, or
- Classes deriving from
BaseTool/StructuredTool when you need validation, async, robust error handling.
-
Group related tools with a Toolkit: Use BaseToolkit to publish a cohesive set of tools with a get_tools() entry point, enabling a single import path for consumers.
class BaseToolkit(BaseModel, ABC):
...
@abstractmethod
def get_tools(self) -> list[BaseTool]:
"""Get all tools in the toolkit."""
Q2 Import strategies (registry/factory)
- Direct imports when the tool set is small/stable.
- Registry/Factory when tools come from many repos or are toggled by config/feature flags. This centralizes instantiation, avoids import cycles, and allows lazy imports for heavy dependencies.
Example registry:
# tools_registry.py
from typing import Iterable
def load_tools() -> Iterable:
from external_tools_repo import DatabaseTool, APITool
from my_custom_package.tools import WeatherTool
return [DatabaseTool(), APITool(), WeatherTool()]
Q3 Configuration and credentials
- Environment variables for keys/secrets (e.g.,
API_KEY, SERVICE_URL). Read them inside the tool constructor.
- Config injection: Accept explicit parameters for endpoints/timeouts; wire via a factory.
- No secrets in code: Keep
.env/secret managers outside VCS (I know, that’s obvious
)
import os
from langchain_core.tools import StructuredTool
from pydantic import BaseModel, Field
class QueryArgs(BaseModel):
query: str = Field(..., description="Search query")
class APITool(StructuredTool):
name = "api_search"
description = "Searches external API"
args_schema = QueryArgs
def __init__(self) -> None:
super().__init__()
self.endpoint = os.environ["API_ENDPOINT"]
self.api_key = os.environ["API_KEY"]
def _run(self, *, query: str, **_) -> str:
# call external API with self.endpoint / self.api_key
return "result"
Q4 Integrating external tools with create_agent
Using LangGraph prebuilt ReAct agent (recommended for production):
from langgraph.prebuilt import create_react_agent
from tools_registry import load_tools
# You can pass a model instance or a model name string (see docs models.md)
model = "anthropic:claude-3-7-sonnet-latest"
tools = list(load_tools())
agent = create_react_agent(model=model, tools=tools, prompt="You are a helpful assistant")
result = agent.invoke({"messages": [{"role": "user", "content": "Find weather in SF"}]})
If using classic LangChain ReAct (not recommended for production), see the deprecation note in the classic implementation:
!!! warning
This implementation is ... older and not well-suited for production ...
we recommend using the `create_react_agent` function from the LangGraph library.
Packaging, distribution, discovery
- Packaging: Ship external tools as installable Python packages with clear
extras for optional dependencies.
- Dependencies: Keep model/provider deps in the consumer app; keep API clients within tool packages.
- Discovery: Expose
load_tools() or a BaseToolkit.get_tools() in each package. Optional: use entry points for plugin-style discovery if you need dynamic loading.
Why this works
create_react_agent accepts Callable tools or BaseTool instances. External location does not matter as long as the objects conform to the tool interface and are importable at runtime.
- Tool types and behaviors are standardized in LangChain Core (
BaseTool, StructuredTool, @tool), ensuring compatibility.
Appendix
Entry points (plugin-style discovery)
Entry points let third‑party packages register a function (e.g., load_tools) that your app can auto‑discover at runtime. This avoids hardcoding imports and enables drop‑in plugins.
# pyproject.toml
[project]
name = "external-tools-weather"
version = "0.1.0"
[project.entry-points."langgraph_tools"]
weather = "external_tools_weather:load_tools"
# external_tools_weather/__init__.py
from typing import Iterable
from langchain_core.tools import tool
@tool
def weather(city: str) -> str:
"""Return current weather for a city."""
return "It's always sunny in SF!"
def load_tools() -> Iterable:
# Return plain callables or BaseTool instances
return [weather]
- Consumer app (discover and load all registered tool factories)
from importlib.metadata import entry_points
from langgraph.prebuilt import create_react_agent
def discover_tools(group: str = "langgraph_tools"):
tools = []
for ep in entry_points(group=group):
factory = ep.load() # resolves "pkg:object" to a callable
tools.extend(factory())
return tools
agent = create_react_agent(
model="anthropic:claude-3-7-sonnet-latest",
tools=discover_tools(),
prompt="You are a helpful assistant",
)
- Notes
- Choose a unique entry point group name (e.g.,
langgraph_tools).
- Each plugin exposes a factory (e.g.,
load_tools) returning a list of tools.
- For Python < 3.10, you may need the backport:
import importlib_metadata as metadata.