Toolsets
DSPy.rb’s Toolset pattern lets you group related tools in a single class. Instead of creating separate tool classes for each operation, you can expose multiple methods from one class as individual tools.
When to Use Toolsets
Use toolsets when you have related operations that share state or logic:
- Memory operations - store, retrieve, search, delete
- File operations - read, write, list, delete
- API clients - get, post, put, delete
- Database operations - query, insert, update, delete
Basic Usage
class MyToolset < DSPy::Tools::Toolset
toolset_name "my_tools"
tool :operation_one, description: "Does something"
tool :operation_two, description: "Does something else"
def operation_one(input:)
# Implementation
end
def operation_two(value:, optional: nil)
# Implementation
end
end
# Use with ReAct agent
toolset = MyToolset.new
agent = DSPy::ReAct.new(
signature: MySignature,
tools: toolset.class.to_tools
)
Memory Toolset Example
The included MemoryToolset
shows how to implement a working toolset:
memory = DSPy::Tools::MemoryToolset.new
# The LLM sees these individual tools:
# - memory_store
# - memory_retrieve
# - memory_search
# - memory_list
# - memory_update
# - memory_delete
# - memory_clear
# - memory_count
# - memory_get_metadata
agent = DSPy::ReAct.new(
signature: QASignature,
tools: memory.class.to_tools
)
How It Works
- Toolset class defines methods and exposes them as tools
- ToolProxy wraps each method to act like a standard tool
- Schema generation uses Sorbet signatures to create JSON schemas
- ReAct integration works with existing agents
DSL Methods
toolset_name(name)
Sets the prefix for generated tool names:
class DatabaseToolset < DSPy::Tools::Toolset
toolset_name "db"
tool :query # Creates tool named "db_query"
end
tool(method_name, options)
Exposes a method as a tool:
tool :search,
tool_name: "custom_search", # Override default name
description: "Search for items"
Type Safety
Methods use Sorbet signatures for automatic schema generation:
sig { params(key: String, value: String, tags: T.nilable(T::Array[String])).returns(String) }
def store(key:, value:, tags: nil)
# Implementation
end
This generates:
{
"parameters": {
"properties": {
"key": { "type": "string" },
"value": { "type": "string" },
"tags": { "type": "array", "items": { "type": "string" } }
},
"required": ["key", "value"]
}
}
Memory Operations
The MemoryToolset
provides these operations:
store(key:, value:, tags: nil)
- Store key-value pairs with optional tagsretrieve(key:)
- Get value by keysearch(pattern:, in_keys: true, in_values: true)
- Pattern-based searchlist_keys()
- List all keysupdate(key:, value:)
- Update existing memorydelete(key:)
- Delete by keyclear()
- Remove all memoriescount()
- Count stored itemsget_metadata(key:)
- Get metadata (timestamps, access count)
LLM Usage
The LLM interacts with each method as a separate tool:
{
"thought": "I need to store this information",
"action": "memory_store",
"action_input": {
"key": "user_preference",
"value": "dark mode",
"tags": ["ui", "preferences"]
}
}
Testing
Test toolsets like regular Ruby classes:
RSpec.describe MyToolset do
let(:toolset) { described_class.new }
it "performs operations" do
result = toolset.operation_one(input: "test")
expect(result).to eq("expected")
end
it "generates correct tools" do
tools = described_class.to_tools
expect(tools.map(&:name)).to include("my_tools_operation_one")
end
end
Limitations
- Methods must use keyword arguments for schema generation
- Each method becomes a separate tool (no method chaining)
- Shared state is isolated per toolset instance
Next Steps
The toolset pattern works with the implemented memory system. The MemoryToolset
provides basic in-memory storage with operations like store, retrieve, search, and metadata tracking.
For production use, consider implementing custom toolsets that integrate with your preferred storage backend (database, Redis, etc.) by extending the Toolset
base class.
Design Decisions
Explicit Tool Exposure: The tool
DSL requires explicit method declaration rather than auto-exposing all public methods. This ensures:
- Clear documentation for each tool via the
description
parameter - Intentional tool interface design
- Proper schema descriptions for LLM consumption
- Type safety through Sorbet signatures