Skip to main content

Production-ready tool integration library for LLMs

Project description

Sandshrew

A lightweight Python library using a decorator-based approach to:

  • Prepare LLM Provider Agnostic Tool-Function Descriptions without duplication using Pydantic Field Notation
  • Execute tool_calls Sequentially as well as In Parallel depending on use case
  • Internal retry mechanism with a very consistent tool output consisting of Tool-Response and Error Response
  • Support for using injected-state inside tools which are not generated as inputs from LLM calls
  • Helpers for LLM Provider Agnostic Tool-Call Extractions and Finish Loop

Quick Start

Defining Tools (@sand_tool)

For the function that needs to be converted to a tool, attach @sand_tool to it.

Simple Tool Example
from sandshrew import sand_tool
from pydantic import Field

@sand_tool(
    name="custom_name",                   # Custom name for the tool (defaults to function name)
    description="custom description",     # Custom description (defaults to function docstring)
    tags=["tag1", "tag2"],                # Optional List of tags for categorizing the tool
    retry_count=3,                        # Number of retries on failure (default: 0)
    timeout=30.0,                         # Execution timeout in seconds (default: None)
    inject_state=False,                   # Whether to inject state as first parameter (default: False)
)
def add(a: int = Field(description="First number"),
        b: int = Field(description="Second number"),
) -> int:
    """Add two numbers together."""
    return a + b
Optional Arguments
  1. name - Custom name for the tool (defaults to function name)
  2. description - Custom description (defaults to function docstring)
  3. tags - List of tags for categorizing the tool
  4. retry_count - Number of retries on failure (default: 0)
  5. timeout - Execution timeout in seconds (default: None)
  6. inject_state - Whether to inject state as first parameter (default: False)
Tool with injected state

๐Ÿ’ก For an argument to be injected state, ensure the following:

  • The argument is the first argument in the function
  • The argument name begins with _injected_state
from sandshrew import sand_tool
from pydantic import Field

@sand_tool(inject_state=True, tags=["email"])
def send_email(_injected_state: Dict[str, Any], content: str) -> str:
    """
    Send an email using injected state.
    Expected state:
        {
            "user_email": "user@example.com"
        }
    """
    user_email = _injected_state.get("user_email")
    if not user_email:
        return "no user email found in state."

    # Placeholder for actual email logic
    message = f"Sent {content} message to {user_email}..."
    return message

Getting tool-descriptions (LLM Tool Representations)

helpers.py has provider-agnostic methods that can be used to prepare tool-descriptions for any provider.

from sandshrew.helpers import prepare_tools
from sandshrew.data_types import Provider


tool_list = [add, send_email]
tools = prepare_tools(Provider.OPENAI, tool_list)

Expected arguments:

  1. provider - Name of the provider we want to prepare the tool-function descriptions for
  2. tool_list - List of @sand_tool decorated BaseTool(s)

Expected Output format

  1. List of Dict[str, Any] representing tool-call

One click Tool Execution with Injected State

from sandshrew.helpers import Executor


provider_completion_response = client.chat.completions.create(
    model="gpt-4o-mini",
    tools=tools,
    messages=messages,
)


results = Executor(
    tool_list=tool_list,
    provider=Provider.OPENAI,
    _injected_state=_injected_state,
    use_parallel=False,
).execute(provider_completion_response)

Internal Flow

  1. Based on provider, extracts the tool-calls if available
  2. Wraps the injected state on arguments of the tool-calls that have inject_state = True and invokes the actual tool-calls
  3. In cases of errors, retries up to retry_count times for the tool and then returns a defined error (See Expected Output format)
  4. On success, returns a defined success output (See Expected Output format)

Expected arguments:

  1. tool_list - List of @sand_tool decorated BaseTool(s)
  2. provider - Name of the provider for which to prepare tool-function descriptions
  3. _injected_state - Optional object representing the injected state the invoked tools may need
  4. use_parallel - If number of tool_calls > 1, execute them in parallel (Default = False)

Expected Output format

  1. List of ExecutionResult with the format of
class ExecutionResult:
    tool_call: ToolCall
    content: Optional[Any] = None
    error: Optional[ExecutionError] = None

where ToolCall is the input ToolCall and the optional error is of the format ExecutionError

@dataclass(frozen=True)
class ToolCall:
    id: str
    name: str
    arguments: dict[str, Any]


@dataclass
class ExecutionError:
    message: str
    raw_error: Optional[Exception] = None
    retryable: bool = False
    backtrace: Optional[Any] = None

The type of content is Any, making it extensible for other use cases

Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                     LLM Provider Response                        โ”‚
โ”‚  (tool_calls with id, name, arguments from LLM)                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚
                         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Executor.execute()                            โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ 1. Extract tool-calls based on provider                 โ”‚   โ”‚
โ”‚  โ”‚ 2. Inject state for tools with inject_state=True        โ”‚   โ”‚
โ”‚  โ”‚ 3. Execute sequentially or in parallel                  โ”‚   โ”‚
โ”‚  โ”‚ 4. Retry on failure (up to retry_count)                 โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                         โ”‚
                         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   ExecutionResult[]                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
โ”‚  โ”‚ {                                                        โ”‚   โ”‚
โ”‚  โ”‚   tool_call: ToolCall,                                  โ”‚   โ”‚
โ”‚  โ”‚   content: Any,           # Success output              โ”‚   โ”‚
โ”‚  โ”‚   error: ExecutionError   # Error details (if failed)   โ”‚   โ”‚
โ”‚  โ”‚ }                                                        โ”‚   โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Output Format: ExecutionResult

@dataclass
class ExecutionResult:
    tool_call: ToolCall                    # Original tool call
    content: Optional[Any] = None          # Tool execution result
    error: Optional[ExecutionError] = None # Error if execution failed

# Unified Provider Agnostic ToolCall
@dataclass(frozen=True)
class ToolCall:
    id: str                      # Unique identifier from LLM
    name: str                    # Tool function name
    arguments: dict[str, Any]    # Arguments to pass to tool


@dataclass
class ExecutionError:
    message: str                           # Error description
    raw_error: Optional[Exception] = None  # Original exception
    retryable: bool = False                # Whether error is retryable
    backtrace: Optional[Any] = None        # Stack trace

Examples

See example/example_tools.py and example/main.py for complete working examples including:

  • Math operations (add, subtract, multiply, divide)
  • String utilities (greet, validate_email)
  • Stateful tools (send_email, process_with_contextual_state)
  • ReAct-style LLM interactions

Architecture diagram

Development

Setup

Create virtual environment

python3 -m venv venv 
source venv/bin/activate

Install requirements

pip install -r requirements.txt

Running examples

python -B -m example.main

Running Tests

pytest tests/

Code Quality

ruff check .
ruff format .

Contributing

See CONTRIBUTING.md for guidelines.

License

See LICENSE for details.

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

sandshrew-0.1.0.tar.gz (12.7 kB view details)

Uploaded Source

Built Distribution

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

sandshrew-0.1.0-py3-none-any.whl (10.6 kB view details)

Uploaded Python 3

File details

Details for the file sandshrew-0.1.0.tar.gz.

File metadata

  • Download URL: sandshrew-0.1.0.tar.gz
  • Upload date:
  • Size: 12.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.5

File hashes

Hashes for sandshrew-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4896ee8d5a69045da419a5df108f21447150a5e4e4a7f1b60770d94ff36d1715
MD5 32b7666eb6cbf69417d1b0c0bf254e02
BLAKE2b-256 79f41d54b6e9702cdde950beac3f621b2ebd50b2cd2386d98f021a25f6c67ab7

See more details on using hashes here.

File details

Details for the file sandshrew-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: sandshrew-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 10.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.5

File hashes

Hashes for sandshrew-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e9ed31e8231d886af554b4722f6688b4004f47a6fb4de0e5dfbb26f04d40be38
MD5 b2dd3d4019104a8ae98d8b450e73e56b
BLAKE2b-256 f1e37e2b8da5ac66b093c9f6822ce9d42c1b42a62803176e21ac33636ea5adff

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