Skip to content

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:

registry.ptc.enable()  # registers "programmatic_tool_call" tool
# pip install toolregistry[ptc]

See Programmatic Tool Calling guide for details.

New: registry.invoke()

Single-tool execution with full pipeline (permissions, logging):

result = registry.invoke("add", {"a": 1, "b": 2})

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:

from toolregistry.runtimes import CodeResult, CodeRuntime  # ← ImportError

After:

from codecell import CodeResult, SubprocessRuntime  # pip install codecell

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:

pip install toolregistry[mcp]

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)
  • $ref resolution 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.

# Check your Python version
python --version

# If using conda
conda install python=3.11

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.

# pyproject.toml — if you pinned dill
 dependencies = [
     "toolregistry>=0.6.0",
-    "dill>=0.4.0",
 ]

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:

registry.register_from_class(MyClass, traverse_mro=False)

Hub Package Split

The toolregistry[hub] optional extra has been removed. Install hub tools as a separate package:

- pip install toolregistry[hub]
+ pip install toolregistry toolregistry-hub

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.