Packages

A well-typed, idiomatic Gleam client for Anthropic's Claude API with streaming support and tool use

Current section

Files

Jump to
anthropic_gleam src examples tool_use_integration_test.gleam
Raw

src/examples/tool_use_integration_test.gleam

//// Tool Use Integration Test for Anthropic API
////
//// This script tests the complete tool use workflow with the Claude API
//// Run with: gleam run -m examples/tool_use_integration_test
import anthropic/api
import anthropic/client
import anthropic/config
import anthropic/tools.{
build_tool_result_messages, dispatch_tool_calls, extract_tool_calls,
needs_tool_execution,
}
import anthropic/tools/builder.{
add_enum_param, add_string_param, build, tool_builder, with_description,
}
import anthropic/types/error
import anthropic/types/message
import anthropic/types/request
import anthropic/types/tool.{Any, Auto, ToolName}
import gleam/erlang/charlist
import gleam/int
import gleam/io
import gleam/list
import gleam/option.{None, Some}
import gleam/string
/// Main entry point
pub fn main() {
io.println("===========================================")
io.println("Anthropic Tool Use Integration Test")
io.println("===========================================")
io.println("")
// Get API key
case get_api_key() {
Error(Nil) -> {
io.println("ERROR: ANTHROPIC_API_KEY environment variable not set")
io.println("Please set it and try again:")
io.println(" export ANTHROPIC_API_KEY=your-api-key")
}
Ok(api_key) -> {
io.println(
"API Key: [REDACTED - " <> string.slice(api_key, 0, 8) <> "...]",
)
io.println("")
run_tests(api_key)
}
}
}
fn run_tests(api_key: String) {
// Test 1: Tool definition and request with Auto choice
io.println("-------------------------------------------")
io.println("TEST 1: Tool Definition with Auto Choice")
io.println("-------------------------------------------")
let r1 = test_tool_definition_auto(api_key)
case r1 {
True -> io.println("TEST 1: PASSED")
False -> io.println("TEST 1: FAILED")
}
io.println("")
// Test 2: Forced tool use with ToolName choice
io.println("-------------------------------------------")
io.println("TEST 2: Forced Tool Use (ToolName Choice)")
io.println("-------------------------------------------")
let r2 = test_forced_tool_use(api_key)
case r2 {
True -> io.println("TEST 2: PASSED")
False -> io.println("TEST 2: FAILED")
}
io.println("")
// Test 3: Complete tool use workflow (tool_use -> execute -> tool_result)
io.println("-------------------------------------------")
io.println("TEST 3: Complete Tool Use Workflow")
io.println("-------------------------------------------")
let r3 = test_complete_workflow(api_key)
case r3 {
True -> io.println("TEST 3: PASSED")
False -> io.println("TEST 3: FAILED")
}
io.println("")
// Test 4: Multiple tools with Any choice
io.println("-------------------------------------------")
io.println("TEST 4: Multiple Tools with Any Choice")
io.println("-------------------------------------------")
let r4 = test_multiple_tools(api_key)
case r4 {
True -> io.println("TEST 4: PASSED")
False -> io.println("TEST 4: FAILED")
}
io.println("")
// Test 5: Tool builder API validation
io.println("-------------------------------------------")
io.println("TEST 5: Tool Builder API")
io.println("-------------------------------------------")
let r5 = test_tool_builder(api_key)
case r5 {
True -> io.println("TEST 5: PASSED")
False -> io.println("TEST 5: FAILED")
}
// Calculate results
let results = [r1, r2, r3, r4, r5]
let passed = list.filter(results, fn(r) { r }) |> list.length
let failed = list.filter(results, fn(r) { !r }) |> list.length
io.println("")
io.println("===========================================")
io.println("Tool Use Integration Tests Complete")
io.println("===========================================")
io.println(
"Results: "
<> int.to_string(passed)
<> " passed, "
<> int.to_string(failed)
<> " failed",
)
}
fn test_tool_definition_auto(api_key: String) -> Bool {
io.println("Testing tool definition with Auto choice...")
let config_result =
config.config_options()
|> config.with_api_key(api_key)
|> config.load_config()
case config_result {
Error(err) -> {
io.println("Config error: " <> error.error_to_string(err))
False
}
Ok(cfg) -> {
let api_client = client.new(cfg)
// Build a simple calculator tool using builder API
let calculator_tool =
tool_builder("calculator")
|> with_description("Perform basic arithmetic operations")
|> add_string_param(
"operation",
"The operation: add, subtract, multiply, divide",
True,
)
|> add_string_param("a", "First number as a string", True)
|> add_string_param("b", "Second number as a string", True)
|> build()
let req =
request.create_request(
"claude-3-5-haiku-20241022",
[message.user_message("What is 15 + 27? Use the calculator tool.")],
200,
)
|> request.with_tools([calculator_tool])
|> request.with_tool_choice(Auto)
io.println("")
io.println("REQUEST:")
io.println(" Model: claude-3-5-haiku-20241022")
io.println(" Tool: calculator (add, subtract, multiply, divide)")
io.println(" Tool Choice: Auto")
io.println(" Message: What is 15 + 27? Use the calculator tool.")
io.println("")
io.println("Sending request to API...")
io.println("")
case api.create_message(api_client, req) {
Ok(response) -> {
io.println("RESPONSE:")
io.println(" ID: " <> response.id)
io.println(" Model: " <> response.model)
io.println(
" Stop reason: " <> stop_reason_to_string(response.stop_reason),
)
io.println(
" Input tokens: " <> int.to_string(response.usage.input_tokens),
)
io.println(
" Output tokens: " <> int.to_string(response.usage.output_tokens),
)
// Check if tool use was triggered
let is_tool_use = needs_tool_execution(response)
io.println(" Needs tool execution: " <> string.inspect(is_tool_use))
case is_tool_use {
True -> {
let tool_calls = extract_tool_calls(response)
io.println(
" Tool calls count: " <> int.to_string(list.length(tool_calls)),
)
list.each(tool_calls, fn(call) {
io.println(" Tool call:")
io.println(" ID: " <> call.id)
io.println(" Name: " <> call.name)
io.println(" Input: " <> call.input)
})
True
}
False -> {
io.println(" Text response: " <> request.response_text(response))
// Still a pass if the model chose not to use the tool
True
}
}
}
Error(err) -> {
io.println("ERROR: " <> error.error_to_string(err))
False
}
}
}
}
}
fn test_forced_tool_use(api_key: String) -> Bool {
io.println("Testing forced tool use with ToolName choice...")
let config_result =
config.config_options()
|> config.with_api_key(api_key)
|> config.load_config()
case config_result {
Error(err) -> {
io.println("Config error: " <> error.error_to_string(err))
False
}
Ok(cfg) -> {
let api_client = client.new(cfg)
// Build a weather tool
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()
let req =
request.create_request(
"claude-3-5-haiku-20241022",
[message.user_message("Tell me about the weather in Tokyo.")],
200,
)
|> request.with_tools([weather_tool])
|> request.with_tool_choice(ToolName("get_weather"))
io.println("")
io.println("REQUEST:")
io.println(" Model: claude-3-5-haiku-20241022")
io.println(" Tool: get_weather")
io.println(" Tool Choice: ToolName(\"get_weather\") - FORCED")
io.println(" Message: Tell me about the weather in Tokyo.")
io.println("")
io.println("Sending request to API...")
io.println("")
case api.create_message(api_client, req) {
Ok(response) -> {
io.println("RESPONSE:")
io.println(" ID: " <> response.id)
io.println(
" Stop reason: " <> stop_reason_to_string(response.stop_reason),
)
io.println(
" Input tokens: " <> int.to_string(response.usage.input_tokens),
)
io.println(
" Output tokens: " <> int.to_string(response.usage.output_tokens),
)
let is_tool_use = needs_tool_execution(response)
io.println(" Needs tool execution: " <> string.inspect(is_tool_use))
case is_tool_use {
True -> {
let tool_calls = extract_tool_calls(response)
io.println(
" Tool calls count: " <> int.to_string(list.length(tool_calls)),
)
list.each(tool_calls, fn(call) {
io.println(" Tool call:")
io.println(" ID: " <> call.id)
io.println(" Name: " <> call.name)
io.println(" Input: " <> call.input)
})
// Verify the tool was called
case list.first(tool_calls) {
Ok(call) -> call.name == "get_weather"
Error(_) -> False
}
}
False -> {
io.println("ERROR: Expected tool_use but got text response")
False
}
}
}
Error(err) -> {
io.println("ERROR: " <> error.error_to_string(err))
False
}
}
}
}
}
fn test_complete_workflow(api_key: String) -> Bool {
io.println("Testing complete tool use workflow...")
io.println("(tool_use -> execute -> tool_result -> final response)")
let config_result =
config.config_options()
|> config.with_api_key(api_key)
|> config.load_config()
case config_result {
Error(err) -> {
io.println("Config error: " <> error.error_to_string(err))
False
}
Ok(cfg) -> {
let api_client = client.new(cfg)
// Build a simple lookup tool
let lookup_tool =
tool_builder("lookup_capital")
|> with_description("Look up the capital city of a country")
|> add_string_param("country", "The country name", True)
|> build()
let original_messages = [
message.user_message(
"What is the capital of France? Use the lookup tool.",
),
]
let req =
request.create_request(
"claude-3-5-haiku-20241022",
original_messages,
200,
)
|> request.with_tools([lookup_tool])
|> request.with_tool_choice(ToolName("lookup_capital"))
io.println("")
io.println("STEP 1: Initial request with tool")
io.println(
" Message: What is the capital of France? Use the lookup tool.",
)
io.println(" Tool Choice: ToolName(\"lookup_capital\") - FORCED")
io.println("")
io.println("Sending request to API...")
io.println("")
case api.create_message(api_client, req) {
Ok(response) -> {
io.println("STEP 1 RESPONSE:")
io.println(" ID: " <> response.id)
io.println(
" Stop reason: " <> stop_reason_to_string(response.stop_reason),
)
case needs_tool_execution(response) {
True -> {
let tool_calls = extract_tool_calls(response)
io.println(
" Tool calls: " <> int.to_string(list.length(tool_calls)),
)
// Execute tools using dispatch
let handlers = [
#("lookup_capital", fn(_input: String) {
// Simulated lookup - in real usage this would parse input JSON
Ok("{\"capital\": \"Paris\", \"country\": \"France\"}")
}),
]
let results = dispatch_tool_calls(tool_calls, handlers)
io.println("")
io.println("STEP 2: Tool execution")
list.each(results, fn(result) {
case result {
tool.ToolSuccess(id, content) -> {
io.println(" Tool result (success):")
io.println(" ID: " <> id)
io.println(" Content: " <> content)
}
tool.ToolFailure(id, err) -> {
io.println(" Tool result (failure):")
io.println(" ID: " <> id)
io.println(" Error: " <> err)
}
}
})
// Build continuation messages
let continuation_messages =
build_tool_result_messages(original_messages, response, results)
io.println("")
io.println("STEP 3: Sending tool results back")
io.println(
" Messages count: "
<> int.to_string(list.length(continuation_messages)),
)
let continuation_req =
request.create_request(
"claude-3-5-haiku-20241022",
continuation_messages,
200,
)
|> request.with_tools([lookup_tool])
case api.create_message(api_client, continuation_req) {
Ok(final_response) -> {
io.println("")
io.println("STEP 3 RESPONSE (Final):")
io.println(" ID: " <> final_response.id)
io.println(
" Stop reason: "
<> stop_reason_to_string(final_response.stop_reason),
)
io.println(
" Content: " <> request.response_text(final_response),
)
io.println(
" Input tokens: "
<> int.to_string(final_response.usage.input_tokens),
)
io.println(
" Output tokens: "
<> int.to_string(final_response.usage.output_tokens),
)
// Verify we got a text response (not tool_use)
case final_response.stop_reason {
Some(request.EndTurn) -> True
Some(request.MaxTokens) -> True
_ -> {
io.println(" Warning: Unexpected stop reason")
True
}
}
}
Error(err) -> {
io.println(
"ERROR in continuation: " <> error.error_to_string(err),
)
False
}
}
}
False -> {
io.println("ERROR: Expected tool_use but got text response")
False
}
}
}
Error(err) -> {
io.println("ERROR: " <> error.error_to_string(err))
False
}
}
}
}
}
fn test_multiple_tools(api_key: String) -> Bool {
io.println("Testing multiple tools with Any choice...")
let config_result =
config.config_options()
|> config.with_api_key(api_key)
|> config.load_config()
case config_result {
Error(err) -> {
io.println("Config error: " <> error.error_to_string(err))
False
}
Ok(cfg) -> {
let api_client = client.new(cfg)
// Build multiple tools
let weather_tool =
tool_builder("get_weather")
|> with_description("Get weather for a location")
|> add_string_param("location", "City name", True)
|> build()
let time_tool =
tool_builder("get_time")
|> with_description("Get current time in a timezone")
|> add_string_param(
"timezone",
"Timezone name like 'America/New_York'",
True,
)
|> build()
let stock_tool =
tool_builder("get_stock_price")
|> with_description("Get stock price for a ticker symbol")
|> add_string_param("symbol", "Stock ticker symbol like 'AAPL'", True)
|> build()
let req =
request.create_request(
"claude-3-5-haiku-20241022",
[message.user_message("I need to know the current time in New York.")],
200,
)
|> request.with_tools([weather_tool, time_tool, stock_tool])
|> request.with_tool_choice(Any)
io.println("")
io.println("REQUEST:")
io.println(" Model: claude-3-5-haiku-20241022")
io.println(" Tools: get_weather, get_time, get_stock_price")
io.println(" Tool Choice: Any - must use one of the tools")
io.println(" Message: I need to know the current time in New York.")
io.println("")
io.println("Sending request to API...")
io.println("")
case api.create_message(api_client, req) {
Ok(response) -> {
io.println("RESPONSE:")
io.println(" ID: " <> response.id)
io.println(
" Stop reason: " <> stop_reason_to_string(response.stop_reason),
)
io.println(
" Input tokens: " <> int.to_string(response.usage.input_tokens),
)
io.println(
" Output tokens: " <> int.to_string(response.usage.output_tokens),
)
let is_tool_use = needs_tool_execution(response)
io.println(" Needs tool execution: " <> string.inspect(is_tool_use))
case is_tool_use {
True -> {
let tool_calls = extract_tool_calls(response)
io.println(
" Tool calls: " <> int.to_string(list.length(tool_calls)),
)
list.each(tool_calls, fn(call) {
io.println(" Tool call:")
io.println(" ID: " <> call.id)
io.println(" Name: " <> call.name)
io.println(" Input: " <> call.input)
})
// Verify the correct tool was selected
case list.first(tool_calls) {
Ok(call) -> {
io.println(
" Selected tool: " <> call.name <> " (expected: get_time)",
)
call.name == "get_time"
}
Error(_) -> False
}
}
False -> {
io.println("ERROR: Any choice should force tool use")
False
}
}
}
Error(err) -> {
io.println("ERROR: " <> error.error_to_string(err))
False
}
}
}
}
}
fn test_tool_builder(api_key: String) -> Bool {
io.println("Testing tool builder API with complex tool...")
let config_result =
config.config_options()
|> config.with_api_key(api_key)
|> config.load_config()
case config_result {
Error(err) -> {
io.println("Config error: " <> error.error_to_string(err))
False
}
Ok(cfg) -> {
let api_client = client.new(cfg)
// Build a complex tool with multiple parameter types
let search_tool =
tool_builder("search_database")
|> with_description("Search a database with various filters")
|> add_string_param("query", "The search query string", True)
|> add_enum_param(
"category",
"Category to search in",
[
"products",
"users",
"orders",
],
False,
)
|> add_enum_param(
"sort_order",
"Sort order for results",
[
"asc",
"desc",
],
False,
)
|> build()
let req =
request.create_request(
"claude-3-5-haiku-20241022",
[
message.user_message(
"Search for 'laptop' in the products category, sorted descending.",
),
],
200,
)
|> request.with_tools([search_tool])
|> request.with_tool_choice(ToolName("search_database"))
io.println("")
io.println("REQUEST:")
io.println(" Model: claude-3-5-haiku-20241022")
io.println(" Tool: search_database (built with builder API)")
io.println(" Parameters:")
io.println(" - query (string, required)")
io.println(" - category (enum: products/users/orders, optional)")
io.println(" - sort_order (enum: asc/desc, optional)")
io.println(" Tool Choice: ToolName(\"search_database\") - FORCED")
io.println(
" Message: Search for 'laptop' in the products category, sorted descending.",
)
io.println("")
io.println("Sending request to API...")
io.println("")
case api.create_message(api_client, req) {
Ok(response) -> {
io.println("RESPONSE:")
io.println(" ID: " <> response.id)
io.println(
" Stop reason: " <> stop_reason_to_string(response.stop_reason),
)
io.println(
" Input tokens: " <> int.to_string(response.usage.input_tokens),
)
io.println(
" Output tokens: " <> int.to_string(response.usage.output_tokens),
)
let is_tool_use = needs_tool_execution(response)
io.println(" Needs tool execution: " <> string.inspect(is_tool_use))
case is_tool_use {
True -> {
let tool_calls = extract_tool_calls(response)
list.each(tool_calls, fn(call) {
io.println(" Tool call:")
io.println(" ID: " <> call.id)
io.println(" Name: " <> call.name)
io.println(" Input: " <> call.input)
})
// Verify tool was called with correct structure
case list.first(tool_calls) {
Ok(call) -> {
let has_query = string.contains(call.input, "laptop")
let has_category = string.contains(call.input, "products")
io.println("")
io.println(" Validation:")
io.println(
" Contains 'laptop': " <> string.inspect(has_query),
)
io.println(
" Contains 'products': " <> string.inspect(has_category),
)
call.name == "search_database" && has_query
}
Error(_) -> False
}
}
False -> {
io.println("ERROR: Expected tool_use response")
False
}
}
}
Error(err) -> {
io.println("ERROR: " <> error.error_to_string(err))
False
}
}
}
}
}
fn stop_reason_to_string(reason: option.Option(request.StopReason)) -> String {
case reason {
None -> "none"
Some(r) -> request.stop_reason_to_string(r)
}
}
/// Get environment variable using charlist conversion
@external(erlang, "os", "getenv")
fn ffi_getenv(
name: charlist.Charlist,
default: charlist.Charlist,
) -> charlist.Charlist
fn get_api_key() -> Result(String, Nil) {
let value =
ffi_getenv(
charlist.from_string("ANTHROPIC_API_KEY"),
charlist.from_string(""),
)
|> charlist.to_string
case value {
"" -> Error(Nil)
v -> Ok(v)
}
}