class OpenAPIToolkit:
"""Toolkit for managing LangChain tools generated from OpenAPI specs."""
def __init__(
self,
spec: OpenAPISpec,
provider: RequestProvider | None = None,
middleware: Sequence[Middleware] | None = None,
timeout: float = 30.0,
base_url: str | None = None,
) -> None:
self.spec = spec
effective_base_url = base_url
if not effective_base_url and spec.servers:
effective_base_url = spec.servers[0]
self.executor = AsyncHTTPExecutor(
base_url=effective_base_url,
provider=provider,
middleware=middleware,
timeout=timeout,
)
self.factory = LangChainToolFactory(executor=self.executor)
parser = OpenAPIParser(spec)
operations = parser.parse()
self._tools_dict: dict[str, BaseTool] = {}
used_names: set[str] = set()
for op in operations:
base_name = format_tool_name(op)
candidate_name = base_name
counter = 2
while candidate_name in used_names:
candidate_name = f"{base_name}_{counter}"
counter += 1
used_names.add(candidate_name)
tool = self.factory.create_tool(op, name_override=candidate_name)
self._tools_dict[candidate_name] = tool
logger.info("OpenAPIToolkit initialized with %d tools.", len(self._tools_dict))
@classmethod
def from_url(
cls,
url: str,
headers: dict[str, str] | None = None,
provider: RequestProvider | None = None,
middleware: Sequence[Middleware] | None = None,
timeout: float = 30.0,
base_url: str | None = None,
) -> "OpenAPIToolkit":
loader = OpenAPILoader.from_url(url, headers=headers)
spec = loader.load()
return cls(
spec=spec,
provider=provider,
middleware=middleware,
timeout=timeout,
base_url=base_url,
)
@classmethod
def from_file(
cls,
file_path: str | Path,
provider: RequestProvider | None = None,
middleware: Sequence[Middleware] | None = None,
timeout: float = 30.0,
base_url: str | None = None,
) -> "OpenAPIToolkit":
loader = OpenAPILoader.from_file(file_path)
spec = loader.load()
return cls(
spec=spec,
provider=provider,
middleware=middleware,
timeout=timeout,
base_url=base_url,
)
@classmethod
def from_dict(
cls,
spec_dict: dict[str, Any],
provider: RequestProvider | None = None,
middleware: Sequence[Middleware] | None = None,
timeout: float = 30.0,
base_url: str | None = None,
) -> "OpenAPIToolkit":
loader = OpenAPILoader.from_dict(spec_dict)
spec = loader.load()
return cls(
spec=spec,
provider=provider,
middleware=middleware,
timeout=timeout,
base_url=base_url,
)
@classmethod
def from_spec(
cls,
spec: OpenAPISpec,
provider: RequestProvider | None = None,
middleware: Sequence[Middleware] | None = None,
timeout: float = 30.0,
base_url: str | None = None,
) -> "OpenAPIToolkit":
return cls(
spec=spec,
provider=provider,
middleware=middleware,
timeout=timeout,
base_url=base_url,
)
def list_tools(self) -> list[str]:
return list(self._tools_dict.keys())
def get_tool(self, name: str) -> BaseTool:
if name not in self._tools_dict:
available = self.list_tools()
raise KeyError(
f"Tool '{name}' not found in toolkit. Available tools: {available}"
)
return self._tools_dict[name]
def get_tools(
self,
methods: list[str] | None = None,
tags: list[str] | None = None,
include: list[str] | None = None,
exclude: list[str] | None = None,
) -> list[BaseTool]:
result: list[BaseTool] = []
target_methods = [m.upper() for m in methods] if methods else None
inc_set = set(include) if include else None
exc_set = set(exclude) if exclude else None
for tool_name, tool in self._tools_dict.items():
meta = tool.metadata or {}
op_id = meta.get("operation_id")
method = meta.get("method")
op_tags = meta.get("tags") or []
if exc_set and (tool_name in exc_set or (op_id and op_id in exc_set)):
continue
if inc_set and not (tool_name in inc_set or (op_id and op_id in inc_set)):
continue
if target_methods and method not in target_methods:
continue
if tags and not any(t in op_tags for t in tags):
continue
result.append(tool)
return result