Tool calling

Custom Tools

Define your own tools that Claude calls and your application executes:

import sttp.ai.claude.models.{Tool, ToolInputSchema, PropertySchema}

val weatherTool = Tool(
  name = "get_weather",
  description = "Get current weather for a location",
  inputSchema = ToolInputSchema(
    `type` = "object",
    properties = Map(
      "location" -> PropertySchema(`type` = "string", description = Some("City name")),
      "unit" -> PropertySchema(`type` = "string", `enum` = Some(List("celsius", "fahrenheit")))
    ),
    required = Some(List("location"))
  )
)

val request = MessageRequest.withTools(
  model = "claude-3-sonnet-20240229",
  messages = List(Message.user(List(ContentBlock.text("What's the weather in Paris?")))),
  maxTokens = 1000,
  tools = List(weatherTool)
)

Predefined Tools

Currently supported:

  • Tool.WebSearch (web_search_20250305)

import sttp.ai.claude.models.{ContentBlock, Message, Tool}
import sttp.ai.claude.requests.MessageRequest

val request = MessageRequest.withTools(
  model = "claude-sonnet-4-5-20250514",
  messages = List(Message.user(List(ContentBlock.text("What was the most recent SpaceX launch?")))),
  maxTokens = 1024,
  tools = List(Tool.WebSearch.default)
)

val response = client.createMessage(request)

response.content.foreach {
  case t: ContentBlock.Text              => println(t.text)
  case s: ContentBlock.ServerToolUse     =>
    println(s"Searched for: ${s.input.get("query").map(_.str).getOrElse("")}")
  case r: ContentBlock.WebSearchToolResult =>
    r.content match {
      case ContentBlock.WebSearchToolResultBlock.Results(items) =>
        items.foreach(it => println(s"- ${it.title}${it.url}"))
      case ContentBlock.WebSearchToolResultBlock.Error(code) =>
        println(s"Web search failed: $code")
    }
  case _                                        => ()
}

Both custom and predefined tools can be passed in the same tools list.