Packages
A well-typed, idiomatic Gleam client for Anthropic's Claude API with streaming support and tool use
Current section
Files
Jump to
Current section
Files
src/anthropic/tools/builder.gleam
//// Fluent builder for tool definitions
////
//// This module provides an ergonomic builder API for creating tool definitions.
//// The builder pattern allows for type-safe, readable tool construction.
////
//// ## Example
////
//// ```gleam
//// let weather_tool =
//// tool_builder("get_weather")
//// |> with_description("Get the current weather for a location")
//// |> add_string_param("location", "City and state, e.g. 'San Francisco, CA'", True)
//// |> add_enum_param("unit", "Temperature unit", ["celsius", "fahrenheit"], False)
//// |> build()
////
//// // For tools with no parameters
//// let time_tool =
//// tool_builder("get_time")
//// |> with_description("Get the current time")
//// |> build_simple()
//// ```
import anthropic/types/tool.{
type PropertySchema, type Tool, InputSchema, PropertySchema, Tool,
}
import gleam/list
import gleam/option.{type Option, None, Some}
// =============================================================================
// Builder Types
// =============================================================================
/// Builder state for constructing a Tool
pub type ToolBuilder {
ToolBuilder(
/// Name of the tool
name: String,
/// Optional description
description: Option(String),
/// Properties accumulated so far
properties: List(#(String, PropertySchema)),
/// Required property names
required: List(String),
)
}
// =============================================================================
// Builder Initialization
// =============================================================================
/// Start building a new tool with the given name
pub fn tool_builder(name: String) -> ToolBuilder {
ToolBuilder(name: name, description: None, properties: [], required: [])
}
/// Start building a new tool with name and description
pub fn tool_builder_with_description(
name: String,
description: String,
) -> ToolBuilder {
ToolBuilder(
name: name,
description: Some(description),
properties: [],
required: [],
)
}
// =============================================================================
// Builder Methods
// =============================================================================
/// Set the description for the tool
pub fn with_description(
builder: ToolBuilder,
description: String,
) -> ToolBuilder {
ToolBuilder(..builder, description: Some(description))
}
/// Add a string parameter
pub fn add_string_param(
builder: ToolBuilder,
name: String,
description: String,
is_required: Bool,
) -> ToolBuilder {
let prop =
PropertySchema(
property_type: "string",
description: Some(description),
enum_values: None,
items: None,
properties: None,
required: None,
)
add_property(builder, name, prop, is_required)
}
/// Add a number parameter
pub fn add_number_param(
builder: ToolBuilder,
name: String,
description: String,
is_required: Bool,
) -> ToolBuilder {
let prop =
PropertySchema(
property_type: "number",
description: Some(description),
enum_values: None,
items: None,
properties: None,
required: None,
)
add_property(builder, name, prop, is_required)
}
/// Add an integer parameter
pub fn add_integer_param(
builder: ToolBuilder,
name: String,
description: String,
is_required: Bool,
) -> ToolBuilder {
let prop =
PropertySchema(
property_type: "integer",
description: Some(description),
enum_values: None,
items: None,
properties: None,
required: None,
)
add_property(builder, name, prop, is_required)
}
/// Add a boolean parameter
pub fn add_boolean_param(
builder: ToolBuilder,
name: String,
description: String,
is_required: Bool,
) -> ToolBuilder {
let prop =
PropertySchema(
property_type: "boolean",
description: Some(description),
enum_values: None,
items: None,
properties: None,
required: None,
)
add_property(builder, name, prop, is_required)
}
/// Add an enum parameter (string with allowed values)
pub fn add_enum_param(
builder: ToolBuilder,
name: String,
description: String,
values: List(String),
is_required: Bool,
) -> ToolBuilder {
let prop =
PropertySchema(
property_type: "string",
description: Some(description),
enum_values: Some(values),
items: None,
properties: None,
required: None,
)
add_property(builder, name, prop, is_required)
}
/// Add an array parameter with string items
pub fn add_string_array_param(
builder: ToolBuilder,
name: String,
description: String,
item_description: String,
is_required: Bool,
) -> ToolBuilder {
let item_schema =
PropertySchema(
property_type: "string",
description: Some(item_description),
enum_values: None,
items: None,
properties: None,
required: None,
)
let prop =
PropertySchema(
property_type: "array",
description: Some(description),
enum_values: None,
items: Some(item_schema),
properties: None,
required: None,
)
add_property(builder, name, prop, is_required)
}
/// Add an array parameter with number items
pub fn add_number_array_param(
builder: ToolBuilder,
name: String,
description: String,
is_required: Bool,
) -> ToolBuilder {
let item_schema =
PropertySchema(
property_type: "number",
description: None,
enum_values: None,
items: None,
properties: None,
required: None,
)
let prop =
PropertySchema(
property_type: "array",
description: Some(description),
enum_values: None,
items: Some(item_schema),
properties: None,
required: None,
)
add_property(builder, name, prop, is_required)
}
/// Add a custom property schema
pub fn add_property(
builder: ToolBuilder,
name: String,
property: PropertySchema,
is_required: Bool,
) -> ToolBuilder {
let new_properties = list.append(builder.properties, [#(name, property)])
let new_required = case is_required {
True -> list.append(builder.required, [name])
False -> builder.required
}
ToolBuilder(..builder, properties: new_properties, required: new_required)
}
/// Add an object parameter with nested properties
pub fn add_object_param(
builder: ToolBuilder,
name: String,
description: String,
nested_properties: List(#(String, PropertySchema)),
nested_required: List(String),
is_required: Bool,
) -> ToolBuilder {
let prop =
PropertySchema(
property_type: "object",
description: Some(description),
enum_values: None,
items: None,
properties: Some(nested_properties),
required: Some(nested_required),
)
add_property(builder, name, prop, is_required)
}
// =============================================================================
// Build Methods
// =============================================================================
/// Build the tool from the builder state
pub fn build(builder: ToolBuilder) -> Tool {
let input_schema = case builder.properties {
[] -> InputSchema(schema_type: "object", properties: None, required: None)
props ->
InputSchema(
schema_type: "object",
properties: Some(props),
required: case builder.required {
[] -> None
reqs -> Some(reqs)
},
)
}
Tool(
name: builder.name,
description: builder.description,
input_schema: input_schema,
)
}
/// Build a simple tool with no parameters
pub fn build_simple(builder: ToolBuilder) -> Tool {
Tool(
name: builder.name,
description: builder.description,
input_schema: InputSchema(
schema_type: "object",
properties: None,
required: None,
),
)
}
// =============================================================================
// Validation
// =============================================================================
/// Validation error for tool definitions
pub type ToolBuilderError {
/// Tool name is empty
EmptyName
/// Tool name contains invalid characters
InvalidNameCharacters(name: String)
/// Tool name is too long (max 64 characters)
NameTooLong(name: String, length: Int)
/// Duplicate property name
DuplicateProperty(name: String)
}
/// Validate a tool name according to Anthropic's requirements
/// Must match regex: ^[a-zA-Z0-9_-]{1,64}$
pub fn validate_name(name: String) -> Result(String, ToolBuilderError) {
case name {
"" -> Error(EmptyName)
_ -> {
let length = string_length(name)
case length > 64 {
True -> Error(NameTooLong(name: name, length: length))
False -> {
case is_valid_tool_name(name) {
True -> Ok(name)
False -> Error(InvalidNameCharacters(name: name))
}
}
}
}
}
}
/// Build and validate the tool
pub fn build_validated(builder: ToolBuilder) -> Result(Tool, ToolBuilderError) {
case validate_name(builder.name) {
Error(err) -> Error(err)
Ok(_) -> {
// Check for duplicate properties
let prop_names = list.map(builder.properties, fn(p) { p.0 })
case has_duplicates(prop_names) {
True -> {
let dup = find_first_duplicate(prop_names)
Error(DuplicateProperty(name: option.unwrap(dup, "")))
}
False -> Ok(build(builder))
}
}
}
}
// =============================================================================
// Helper Functions
// =============================================================================
/// Check if a string only contains valid tool name characters
fn is_valid_tool_name(name: String) -> Bool {
name
|> string_to_graphemes
|> list.all(fn(char) { is_alphanumeric(char) || char == "_" || char == "-" })
}
fn is_alphanumeric(char: String) -> Bool {
let lower =
char == "a"
|| char == "b"
|| char == "c"
|| char == "d"
|| char == "e"
|| char == "f"
|| char == "g"
|| char == "h"
|| char == "i"
|| char == "j"
|| char == "k"
|| char == "l"
|| char == "m"
|| char == "n"
|| char == "o"
|| char == "p"
|| char == "q"
|| char == "r"
|| char == "s"
|| char == "t"
|| char == "u"
|| char == "v"
|| char == "w"
|| char == "x"
|| char == "y"
|| char == "z"
let upper =
char == "A"
|| char == "B"
|| char == "C"
|| char == "D"
|| char == "E"
|| char == "F"
|| char == "G"
|| char == "H"
|| char == "I"
|| char == "J"
|| char == "K"
|| char == "L"
|| char == "M"
|| char == "N"
|| char == "O"
|| char == "P"
|| char == "Q"
|| char == "R"
|| char == "S"
|| char == "T"
|| char == "U"
|| char == "V"
|| char == "W"
|| char == "X"
|| char == "Y"
|| char == "Z"
let digit =
char == "0"
|| char == "1"
|| char == "2"
|| char == "3"
|| char == "4"
|| char == "5"
|| char == "6"
|| char == "7"
|| char == "8"
|| char == "9"
lower || upper || digit
}
fn has_duplicates(items: List(String)) -> Bool {
list.length(items) != list.length(list.unique(items))
}
fn find_first_duplicate(items: List(String)) -> Option(String) {
find_first_duplicate_helper(items, [])
}
fn find_first_duplicate_helper(
remaining: List(String),
seen: List(String),
) -> Option(String) {
case remaining {
[] -> None
[first, ..rest] -> {
case list.contains(seen, first) {
True -> Some(first)
False -> find_first_duplicate_helper(rest, [first, ..seen])
}
}
}
}
import gleam/string
fn string_length(s: String) -> Int {
string.length(s)
}
fn string_to_graphemes(s: String) -> List(String) {
string.to_graphemes(s)
}