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
Sequentiallyas well asIn Paralleldepending 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
name- Custom name for the tool (defaults to function name)description- Custom description (defaults to function docstring)tags- List of tags for categorizing the toolretry_count- Number of retries on failure (default: 0)timeout- Execution timeout in seconds (default: None)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:
provider- Name of the provider we want to prepare the tool-function descriptions fortool_list- List of @sand_tool decorated BaseTool(s)
Expected Output format
- 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
- Based on provider, extracts the tool-calls if available
- Wraps the injected state on arguments of the tool-calls that have
inject_state = Trueand invokes the actual tool-calls - In cases of errors, retries up to
retry_counttimes for the tool and then returns a defined error (SeeExpected Output format) - On success, returns a defined success output (See
Expected Output format)
Expected arguments:
tool_list- List of @sand_tool decorated BaseTool(s)provider- Name of the provider for which to prepare tool-function descriptions_injected_state- Optional object representing the injected state the invoked tools may needuse_parallel- If number of tool_calls > 1, execute them in parallel (Default = False)
Expected Output format
- 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
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4896ee8d5a69045da419a5df108f21447150a5e4e4a7f1b60770d94ff36d1715
|
|
| MD5 |
32b7666eb6cbf69417d1b0c0bf254e02
|
|
| BLAKE2b-256 |
79f41d54b6e9702cdde950beac3f621b2ebd50b2cd2386d98f021a25f6c67ab7
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9ed31e8231d886af554b4722f6688b4004f47a6fb4de0e5dfbb26f04d40be38
|
|
| MD5 |
b2dd3d4019104a8ae98d8b450e73e56b
|
|
| BLAKE2b-256 |
f1e37e2b8da5ac66b093c9f6822ce9d42c1b42a62803176e21ac33636ea5adff
|