Migration Guide¶
This guide covers breaking changes and migration steps between major ToolRegistry releases.
0.12.x → 0.13.0¶
New: Programmatic Tool Calling (PTC)¶
LLMs can now write Python code that calls registered tools:
See Programmatic Tool Calling guide for details.
New: registry.invoke()¶
Single-tool execution with full pipeline (permissions, logging):
Replaces direct tool.run() calls when you need permission checks and logging.
New: Invocation tracking¶
Execution log entries now have an invocation_id field:
registry.enable_logging()
registry.invoke("add", {"a": 1, "b": 2})
log = registry.get_execution_log()
entries = log.get_entries(invocation_id="tr_sig_...")
runtimes/ package changes¶
CodeResult and CodeRuntime have been removed from toolregistry.runtimes. They are now provided by the codecell package.
Before:
After:
ToolProjection, DirectProjection, validate_namespace(), and namespace_to_callables() remain in toolregistry.runtimes.
0.11.x → 0.12.0¶
Tool.run() / arun() No Longer Swallow Exceptions¶
Tool.run() and arun() previously caught exceptions and returned error strings like "Error executing tool_name: ...". Starting in v0.12.0, they raise exceptions directly — the same behavior run_raw()/arun_raw() had.
run_raw() and arun_raw() are now deprecated aliases for run() / arun().
Before (v0.10–v0.11):
tool = registry.get_tool("divide")
result = tool.run({"a": 10, "b": 0})
# result == "Error executing divide: division by zero" ← no exception
After (v0.12+):
tool = registry.get_tool("divide")
try:
result = tool.run({"a": 10, "b": 0})
except ZeroDivisionError:
print("Cannot divide by zero!")
# run_raw() still works but emits DeprecationWarning
# Just use run() instead.
Tool.callable Is Now a BaseToolWrapper¶
Tool.callable is no longer a bare Python function for native tools. It is always a BaseToolWrapper subclass. If you need the original unwrapped function, use the new tool.fn property.
Before:
tool = Tool.from_function(my_func)
assert tool.callable == my_func # worked
sig = inspect.signature(tool.callable) # got real signature
After:
tool = Tool.from_function(my_func)
assert tool.fn == my_func # use .fn instead
sig = inspect.signature(tool.fn) # use .fn for introspection
Sync/Async Transparent Calling¶
All tools now support both run() and arun() regardless of whether the underlying function is sync or async:
# Sync tool
sync_tool = Tool.from_function(sync_add)
sync_tool.run({"a": 1, "b": 2}) # direct call
await sync_tool.arun({"a": 1, "b": 2}) # via asyncio.to_thread
# Async tool
async_tool = Tool.from_function(async_add)
async_tool.run({"a": 1, "b": 2}) # via asyncio.run
await async_tool.arun({"a": 1, "b": 2}) # direct await
0.8.x → 0.10.0¶
HttpxClientConfig Renamed to HttpClientConfig¶
The HttpxClientConfig class has been renamed to HttpClientConfig to reflect the removal of httpx as a core dependency. The old name is preserved as a deprecated alias.
Before:
from toolregistry.integrations.openapi import HttpxClientConfig
client_config = HttpxClientConfig(base_url="http://localhost:8000")
After:
from toolregistry.integrations.openapi import HttpClientConfig
client_config = HttpClientConfig(base_url="http://localhost:8000")
The old import still works but emits a DeprecationWarning. Update at your earliest convenience.
httpx Moved to Optional Dependency¶
httpx is no longer a core dependency. It has been moved to the [mcp] optional extras group. If you use MCP integration, install with:
Core OpenAPI functionality now uses a built-in HTTP client internally.
PyYAML and jsonref Removed from OpenAPI Extra¶
PyYAML and jsonref are no longer required for OpenAPI integration. They have been replaced by built-in vendored modules:
- YAML parsing now uses
_vendor.yaml(zerodep) $refresolution now uses_vendor.jsonschema.resolve_refs()(zerodep)
The openapi optional extra is now empty — pip install toolregistry[openapi] is kept for forward compatibility but installs no additional packages. No code changes are needed; this only affects dependency footprint.
0.7.x → 0.8.0¶
Integration Packages Moved Under integrations/¶
All integration sub-packages (mcp, openapi, langchain, native) have been moved under a new integrations/ parent package. This provides a clearer project structure as the number of integrations grows.
Before (0.7.x):
from toolregistry.mcp import MCPClient
from toolregistry.openapi import OpenAPIIntegration
from toolregistry.langchain import LangChainIntegration
from toolregistry.native import NativeIntegration
After (0.8.0):
from toolregistry.integrations.mcp import MCPClient
from toolregistry.integrations.openapi import OpenAPIIntegration
from toolregistry.integrations.langchain import LangChainIntegration
from toolregistry.integrations.native import NativeIntegration
Import path mapping:
| Old path (deprecated) | New canonical path |
|---|---|
toolregistry.mcp |
toolregistry.integrations.mcp |
toolregistry.openapi |
toolregistry.integrations.openapi |
toolregistry.langchain |
toolregistry.integrations.langchain |
toolregistry.native |
toolregistry.integrations.native |
Backward compatibility: The old import paths still work but emit a DeprecationWarning. They will be removed in a future release. Update your imports at your earliest convenience.
# This still works in 0.8.0 but prints a DeprecationWarning:
from toolregistry.mcp import MCPClient
# DeprecationWarning: Importing from 'toolregistry.mcp' is deprecated.
# Use 'toolregistry.integrations.mcp' instead.
Public API unchanged: The ToolRegistry convenience methods — register_from_mcp(), register_from_openapi(), register_from_langchain(), and register_from_native() — continue to work exactly as before with no code changes required.
0.6.x → 0.7.0¶
Executor Backend Architecture¶
The monolithic Executor class has been replaced by a pluggable backend system.
Before (0.6.x):
from toolregistry import ToolRegistry
registry = ToolRegistry()
# parallel_mode parameter controlled execution
results = registry.execute_tool_calls(tool_calls, parallel_mode="thread")
After (0.7.0):
from toolregistry import ToolRegistry
registry = ToolRegistry()
# Use execution_mode parameter or set_default_execution_mode()
registry.set_default_execution_mode("thread") # "thread" or "process"
results = registry.execute_tool_calls(tool_calls)
# Or per-call override
results = registry.execute_tool_calls(tool_calls, execution_mode="thread")
What changed:
| 0.6.x | 0.7.0 | Notes |
|---|---|---|
parallel_mode parameter |
execution_mode parameter |
Renamed |
Executor class |
ThreadBackend / ProcessPoolBackend |
Pluggable backends |
| No cancellation support | ExecutionContext with check_cancelled() |
Cooperative cancellation |
| No timeout per tool | ToolMetadata(timeout=5.0) |
Per-tool timeout enforcement |
New Dependency: llm-rosetta¶
llm-rosetta>=0.2.6 is now a core dependency, powering multi-format schema generation. No action needed — it installs automatically.
New Permission System¶
The permission system is additive and opt-in. Existing code continues to work without changes. To adopt:
from toolregistry import ToolRegistry, ToolMetadata, ToolTag
from toolregistry.permissions import PermissionPolicy, ALLOW_READONLY, ASK_DESTRUCTIVE
# Classify tools with metadata
registry.register(tool, metadata=ToolMetadata(tags=[ToolTag.READ_ONLY]))
# Set permission policy
policy = PermissionPolicy(rules=[ALLOW_READONLY, ASK_DESTRUCTIVE])
registry.set_permission_policy(policy)
Multi-Format Schema Support¶
get_schemas() now accepts api_format values "anthropic" and "gemini" in addition to the existing OpenAI formats.
# New formats
schemas = registry.get_schemas(api_format="anthropic")
schemas = registry.get_schemas(api_format="gemini")
# Existing formats still work
schemas = registry.get_schemas() # default: OpenAI chat completion
schemas = registry.get_schemas(api_format="openai-responses")
0.5.x → 0.6.0¶
Python 3.10+ Required¶
ToolRegistry 0.6.0 drops support for Python 3.8 and 3.9. Update your Python version to 3.10 or later.
dill → cloudpickle¶
The serialization dependency changed from dill to cloudpickle. This is transparent — no code changes needed, but if you pinned dill in your dependencies, you can remove it.
Type Annotation Modernization¶
If you subclass ToolRegistry internals, note that type annotations now use Python 3.10+ syntax:
# 0.5.x style (still works in 3.10+)
from typing import Optional, List, Dict
def func(x: Optional[str] = None) -> List[Dict[str, int]]: ...
# 0.6.x style
def func(x: str | None = None) -> list[dict[str, int]]: ...
0.4.x → 0.5.0¶
register_from_class() MRO Default Changed¶
traverse_mro now defaults to True, meaning inherited methods from parent classes are automatically registered.
Before (0.4.x): Only methods defined directly on the class were registered.
After (0.5.0): Methods from parent classes (excluding object) are also registered.
To restore the old behavior:
Hub Package Split¶
The toolregistry[hub] optional extra has been removed. Install hub tools as a separate package:
The import path from toolregistry.hub import ... still works when both packages are installed.
MCP SDK Change¶
The MCP dependency changed from fastmcp to the official mcp SDK:
- pip install toolregistry[mcp] # installed fastmcp
+ pip install toolregistry[mcp] # now installs mcp>=1.0.0
No code changes needed — the register_from_mcp() API is unchanged. Transport configuration now supports all four transport types: stdio, SSE, streamable-http, and websocket.