Skip to main content

Universal tool-calling wrapper for non-tool-native LLMs — emulates function calling via structured JSON planning

Project description

toolproxy

PyPI version Python Versions License: MIT

Universal Tool-Calling Wrapper for Non-Tool-Native LLMs

A provider-agnostic Python library that adds reliable tool/function calling to any LLM — even models that have no native tool-calling API.


Problem

Many LLM providers (OpenRouter, Ollama, local LLMs) expose models that don't support function calling. This library solves that by:

  • Detecting whether the model supports native tool calling.
  • Using native tool calls when available (OpenAI format).
  • Falling back to a structured JSON planning protocol when not.

The developer always uses the same API regardless of the underlying model.


Installation

pip install toolproxy

Or from source:

pip install -e ".[dev]"

Quick Start

from toolproxy import UniversalAgent, tool

@tool
def get_weather(city: str) -> str:
    """Get the current weather for a city."""
    return f"Sunny, 25°C in {city}"

agent = UniversalAgent(
    model="openrouter/mistralai/mistral-7b-instruct",
    tools=[get_weather],
)

result = agent.run("What is the weather in Chennai today?")
print(result.content)

The same code works whether the model supports native tools or not.


How It Works

Developer
    │
    ▼
UniversalAgent.run(prompt)
    │
    ├─ Planner (auto-detects native vs emulated mode)
    │       │
    │       ├── Native mode  → provider tool calls (OpenAI format)
    │       └── Emulated mode → structured JSON Action schema
    │
    ├─ Executor (validates args, runs tool, captures errors)
    │
    └─ LoopController (repeats until final answer or max_steps)

Model Prefixes

Prefix Backend
openrouter/... OpenRouter API
ollama/... Local Ollama server
mock/... MockClient (for testing, no API key needed)
(no prefix) OpenAI / any OpenAI-compatible endpoint

Advanced Options

from toolproxy import UniversalAgent, tool
from toolproxy.config import ExecutionPolicy

agent = UniversalAgent(
    model="openrouter/your-model",
    tools=[get_weather],
    mode="auto",               # "auto" | "native_only" | "emulated_only"
    max_steps=10,
    execution_policy=ExecutionPolicy(
        mode="allow_only",
        allowed_tools=["get_weather"],
    ),
)

result = agent.run("...", return_trace=True)
print(result.content)
for call in result.trace.tool_calls:
    print(call.tool_name, call.arguments)

Callbacks (streaming-style)

result = agent.run(
    "...",
    on_tool_call=lambda step, tc: print(f"Calling: {tc.tool_name}"),
    on_tool_result=lambda step, tr: print(f"Result: {tr.output}"),
    on_model_output=lambda step, text: print(f"Model: {text}"),
)

Emulated Mode Protocol

When the model does not support native tools, the agent injects a system prompt instructing the model to output one of two JSON formats:

// Tool call
{"type": "tool_call", "tool": {"tool_name": "get_weather", "arguments": {"city": "Chennai"}}}

// Final answer
{"type": "final", "content": "The weather is sunny."}

Malformed responses are retried up to parse_retries times (default: 3) with an error explanation.


Project Structure

src/toolproxy/
  __init__.py      # Public API re-exports
  agent.py         # UniversalAgent class
  llm_client.py    # LLMClient + adapters
  tools.py         # @tool decorator + ToolRegistry
  schemas.py       # Pydantic schemas
  planner.py       # Planner logic
  executor.py      # Tool execution + policies
  loop.py          # Loop controller
  exceptions.py    # Custom exceptions
  config.py        # Configuration + capability map
examples/
  basic_chat.py
  openrouter_tools.py
  local_ollama.py
tests/
  test_agent_basic.py
  test_emulated_mode.py
  test_native_mode.py
  test_error_handling.py
  test_tool_registry.py

Publishing to PyPI

# 1. Install build tools
pip install build twine

# 2. Build wheel + sdist
python -m build

# 3. Check the distribution
twine check dist/*

# 4. Upload to PyPI (you will be prompted for credentials)
twine upload dist/*

# Or upload to TestPyPI first
twine upload --repository testpypi dist/*

Running Tests

pytest tests/ -v

Environment Variables

Variable Description
OPENROUTER_API_KEY API key for OpenRouter
OPENAI_API_KEY API key for OpenAI
OLLAMA_BASE_URL Ollama server URL (default: http://localhost:11434)
OLLAMA_MODEL Ollama model name (default: llama3)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

toolproxy-0.3.0.tar.gz (55.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

toolproxy-0.3.0-py3-none-any.whl (49.6 kB view details)

Uploaded Python 3

File details

Details for the file toolproxy-0.3.0.tar.gz.

File metadata

  • Download URL: toolproxy-0.3.0.tar.gz
  • Upload date:
  • Size: 55.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for toolproxy-0.3.0.tar.gz
Algorithm Hash digest
SHA256 ad1177c3993ec3cb049e5d2e914edf388cc3cbec212689b69ddbfdd368163002
MD5 7225c3ce7202b02b5f0b513b9f35282f
BLAKE2b-256 ab90213c9ae19af1b430df2eb704b18b9079dfb9e15196168aaf10814f1a3d43

See more details on using hashes here.

File details

Details for the file toolproxy-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: toolproxy-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 49.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for toolproxy-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8e8ce6f29ee2d41485471b3673daf42eaece6533779a122967aa4c4308d06be3
MD5 15305ed2d9aa151c632a9a8c2439b2ab
BLAKE2b-256 a2a3475c0fcb91a65e684da7ae30109bfc06a28b8dedf80705125efcf72ff058

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page