claude-1.2.0: examples/claude-tool-search-example/Main.hs
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
-- | Example demonstrating tool search with Claude
--
-- This example shows how to use the server-side tool search feature,
-- which allows Claude to efficiently search through large numbers of tools.
--
-- Tool search requires the beta header: anthropic-beta: advanced-tool-use-2025-11-20
module Main where
import Data.Foldable (toList)
import Data.Text (Text)
import qualified Claude.V1 as V1
import qualified Claude.V1.Messages as Messages
import qualified Claude.V1.Tool as Tool
import qualified Data.Aeson as Aeson
import qualified Data.Text as Text
import qualified Data.Text.IO as Text.IO
import qualified System.Environment as Environment
-- | Define many tools to demonstrate tool search
-- In a real application, you might have hundreds of tools
weatherTool :: Tool.Tool
weatherTool = Tool.functionTool
"get_weather"
(Just "Get the current weather for a location")
$ Aeson.object
[ "properties" Aeson..= Aeson.object
[ "location" Aeson..= Aeson.object
[ "type" Aeson..= ("string" :: Text)
, "description" Aeson..= ("City and state, e.g. San Francisco, CA" :: Text)
]
]
, "required" Aeson..= (["location"] :: [Text])
]
stockPriceTool :: Tool.Tool
stockPriceTool = Tool.functionTool
"get_stock_price"
(Just "Get the current stock price for a ticker symbol")
$ Aeson.object
[ "properties" Aeson..= Aeson.object
[ "ticker" Aeson..= Aeson.object
[ "type" Aeson..= ("string" :: Text)
, "description" Aeson..= ("Stock ticker symbol, e.g. AAPL" :: Text)
]
]
, "required" Aeson..= (["ticker"] :: [Text])
]
currencyConvertTool :: Tool.Tool
currencyConvertTool = Tool.functionTool
"convert_currency"
(Just "Convert an amount from one currency to another")
$ Aeson.object
[ "properties" Aeson..= Aeson.object
[ "amount" Aeson..= Aeson.object
[ "type" Aeson..= ("number" :: Text)
, "description" Aeson..= ("Amount to convert" :: Text)
]
, "from_currency" Aeson..= Aeson.object
[ "type" Aeson..= ("string" :: Text)
, "description" Aeson..= ("Source currency code, e.g. USD" :: Text)
]
, "to_currency" Aeson..= Aeson.object
[ "type" Aeson..= ("string" :: Text)
, "description" Aeson..= ("Target currency code, e.g. EUR" :: Text)
]
]
, "required" Aeson..= (["amount", "from_currency", "to_currency"] :: [Text])
]
calculatorTool :: Tool.Tool
calculatorTool = Tool.functionTool
"calculator"
(Just "Perform basic arithmetic calculations")
$ Aeson.object
[ "properties" Aeson..= Aeson.object
[ "expression" Aeson..= Aeson.object
[ "type" Aeson..= ("string" :: Text)
, "description" Aeson..= ("Math expression to evaluate, e.g. 2+2" :: Text)
]
]
, "required" Aeson..= (["expression"] :: [Text])
]
searchWebTool :: Tool.Tool
searchWebTool = Tool.functionTool
"search_web"
(Just "Search the web for information")
$ Aeson.object
[ "properties" Aeson..= Aeson.object
[ "query" Aeson..= Aeson.object
[ "type" Aeson..= ("string" :: Text)
, "description" Aeson..= ("Search query" :: Text)
]
]
, "required" Aeson..= (["query"] :: [Text])
]
main :: IO ()
main = do
key <- Text.pack <$> Environment.getEnv "ANTHROPIC_KEY"
env <- V1.getClientEnv "https://api.anthropic.com"
-- Use makeMethodsWith to include the beta header for tool search
let options = V1.defaultClientOptions
{ V1.apiKey = key
, V1.anthropicBeta = Just "advanced-tool-use-2025-11-20"
}
let V1.Methods{ V1.createMessage } = V1.makeMethodsWith env options
-- Set up tools with tool search enabled
-- The tool search tool is NOT deferred, while other tools are deferred
let tools =
[ Tool.toolSearchRegex -- Enable regex-based tool search
, Tool.deferredTool weatherTool
, Tool.deferredTool stockPriceTool
, Tool.deferredTool currencyConvertTool
, Tool.deferredTool calculatorTool
, Tool.deferredTool searchWebTool
]
let message = Messages.Message
{ Messages.role = Messages.User
, Messages.content =
[ Messages.textContent "What's the weather like in Tokyo?"
]
, Messages.cache_control = Nothing
}
Text.IO.putStrLn "Sending request with tool search enabled..."
Text.IO.putStrLn $ "Using " <> Text.pack (show (length tools)) <> " tools (with deferred loading)"
response <- createMessage Messages._CreateMessage
{ Messages.model = "claude-sonnet-4-5-20250929"
, Messages.messages = [message]
, Messages.max_tokens = 1024
, Messages.tools = Just tools
}
let Messages.MessageResponse
{ Messages.stop_reason = stopReason
, Messages.content = responseContent
, Messages.usage = Messages.Usage
{ Messages.input_tokens = inputToks
, Messages.output_tokens = outputToks
, Messages.server_tool_use = serverToolUse
}
} = response
Text.IO.putStrLn $ "\nStop reason: " <> Text.pack (show stopReason)
-- Print usage info including tool search requests
Text.IO.putStrLn $ "Input tokens: " <> Text.pack (show inputToks)
Text.IO.putStrLn $ "Output tokens: " <> Text.pack (show outputToks)
case serverToolUse of
Just Messages.ServerToolUseUsage{ Messages.web_search_requests = webReqs, Messages.tool_search_requests = toolReqs } -> do
case webReqs of
Just n -> Text.IO.putStrLn $ "Web search requests: " <> Text.pack (show n)
Nothing -> pure ()
case toolReqs of
Just n -> Text.IO.putStrLn $ "Tool search requests: " <> Text.pack (show n)
Nothing -> pure ()
Nothing -> pure ()
Text.IO.putStrLn "\nResponse content:"
mapM_ printContent (toList responseContent)
-- | Print a content block
printContent :: Messages.ContentBlock -> IO ()
printContent (Messages.ContentBlock_Text{ Messages.text = t }) =
Text.IO.putStrLn $ " [text] " <> t
printContent (Messages.ContentBlock_Tool_Use{ Messages.name = n, Messages.id = tid }) =
Text.IO.putStrLn $ " [tool_use] " <> n <> " (id: " <> tid <> ")"
printContent (Messages.ContentBlock_Server_Tool_Use{ Messages.name = n, Messages.id = tid }) =
Text.IO.putStrLn $ " [server_tool_use] " <> n <> " (id: " <> tid <> ")"
printContent (Messages.ContentBlock_Tool_Search_Tool_Result{ Messages.tool_use_id = tid, Messages.tool_search_content = resultContent }) = do
Text.IO.putStrLn $ " [tool_search_result] for tool_use_id: " <> tid
case resultContent of
Messages.ToolSearchResult{ Messages.tool_references = refs } -> do
Text.IO.putStrLn $ " Found " <> Text.pack (show (length refs)) <> " matching tools:"
mapM_ (\Messages.ToolReference{ Messages.tool_name = tn } ->
Text.IO.putStrLn $ " - " <> tn) (toList refs)
Messages.ToolSearchError{ Messages.error_code = code } ->
Text.IO.putStrLn $ " Error: " <> code
Messages.ToolSearchResultContent_Unknown _ ->
Text.IO.putStrLn " Unknown result type"
printContent (Messages.ContentBlock_Code_Execution_Tool_Result{ Messages.tool_use_id = tid }) =
Text.IO.putStrLn $ " [code_execution_result] for tool_use_id: " <> tid
printContent (Messages.ContentBlock_Thinking{}) =
Text.IO.putStrLn " [thinking]"
printContent (Messages.ContentBlock_Redacted_Thinking{}) =
Text.IO.putStrLn " [redacted_thinking]"
printContent (Messages.ContentBlock_Unknown{ Messages.type_ = t }) =
Text.IO.putStrLn $ " [unknown] type: " <> t