Custom Tools¶
Create custom Spin components to add new tools for dataset generation.
Prerequisites¶
- Spin CLI installed
- Rust toolchain with
wasm32-wasip1target:
Creating a Component¶
1. Initialize Project¶
2. Configure Cargo.toml¶
[package]
name = "my-tools"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
spin-sdk = "3.0"
3. Implement the Handler¶
// src/lib.rs
use anyhow::Result;
use serde::{Deserialize, Serialize};
use spin_sdk::{
http::{Request, Response},
http_component,
};
#[derive(Deserialize)]
struct ExecuteRequest {
session_id: String,
tool: String,
args: serde_json::Value,
}
#[derive(Serialize)]
struct ExecuteResponse {
success: bool,
result: String,
#[serde(skip_serializing_if = "Option::is_none")]
error_type: Option<String>,
}
#[http_component]
fn handle_request(req: Request) -> Result<Response> {
let path = req.path();
match path {
p if p.ends_with("/execute") => handle_execute(req),
p if p.ends_with("/health") => handle_health(),
_ => not_found(),
}
}
fn handle_execute(req: Request) -> Result<Response> {
let request: ExecuteRequest = serde_json::from_slice(req.body())?;
let response = match request.tool.as_str() {
"my_tool" => execute_my_tool(&request),
_ => ExecuteResponse {
success: false,
result: format!("Unknown tool: {}", request.tool),
error_type: Some("UnknownTool".to_string()),
},
};
Ok(Response::builder()
.status(200)
.header("content-type", "application/json")
.body(serde_json::to_vec(&response)?)
.build())
}
fn execute_my_tool(req: &ExecuteRequest) -> ExecuteResponse {
// Your tool logic here
let input = req.args.get("input")
.and_then(|v| v.as_str())
.unwrap_or("default");
ExecuteResponse {
success: true,
result: format!("Processed: {}", input),
error_type: None,
}
}
fn handle_health() -> Result<Response> {
Ok(Response::builder()
.status(200)
.body(r#"{"status":"healthy"}"#)
.build())
}
fn not_found() -> Result<Response> {
Ok(Response::builder()
.status(404)
.body(r#"{"error":"not found"}"#)
.build())
}
4. Register in spin.toml¶
Add to tools-sdk/spin.toml:
[[trigger.http]]
route = "/my-tools/..."
component = "my-tools"
[component.my-tools]
source = "components/my-tools/target/wasm32-wasip1/release/my_tools.wasm"
allowed_outbound_hosts = []
[component.my-tools.build]
command = "cargo build --target wasm32-wasip1 --release"
workdir = "components/my-tools"
5. Build and Run¶
Test your component:
curl -X POST http://localhost:3000/my-tools/execute \
-H "Content-Type: application/json" \
-d '{
"session_id": "test",
"tool": "my_tool",
"args": {"input": "hello"}
}'
Using Custom Tools¶
Register Tool Definition¶
Add to your DeepFabric config:
generation:
tools:
spin_endpoint: "http://localhost:3000"
custom:
- name: my_tool
description: "Process input and return result"
parameters:
- name: input
type: str
required: true
returns: "Processed result"
component: my-tools
The component field specifies which Spin component handles this tool.
External APIs¶
To call external APIs, add allowed hosts:
Then use HTTP in your handler:
use spin_sdk::http::{send, Method, Request as OutRequest};
fn call_external_api() -> Result<String> {
let req = OutRequest::builder()
.method(Method::Get)
.uri("https://api.example.com/data")
.build();
let response = send(req)?;
Ok(String::from_utf8_lossy(response.body()).to_string())
}
Python Components¶
Spin also supports Python via componentize-py. See the GitHub component in tools-sdk/components/github/ for an example.
Best Practices¶
Session isolation: Use session_id to scope any state your tool maintains.
Error handling: Return structured errors with error_type for debugging.
Idempotency: Design tools to be safely retryable when possible.
Documentation: Include clear descriptions in tool definitions for better LLM reasoning.