claude 1.3.0 → 1.3.1
raw patch · 8 files changed
+103/−116 lines, 8 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
Files
- CHANGELOG.md +6/−0
- README.md +0/−13
- claude.cabal +1/−1
- examples/claude-programmatic-tool-calling-example/Main.hs +3/−7
- examples/claude-tool-search-example/Main.hs +4/−7
- src/Claude/V1.hs +3/−3
- src/Claude/V1/Tool.hs +0/−5
- tasty/Main.hs +86/−80
CHANGELOG.md view
@@ -1,3 +1,9 @@+1.3.1:++- Remove `advanced-tool-use-2025-11-20` beta header requirement — tool search,+ programmatic tool calling, and code execution are now generally available+- Fix flaky PTC test by handling non-deterministic model behavior+ 1.3.0: - Add request support for Claude Opus 4.6 routing and speed controls:
README.md view
@@ -96,18 +96,5 @@ - **[Structured Outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)**: Constrain Claude's responses to follow a specific JSON schema, or validate tool parameters with strict mode -The following examples require beta features:--**Tool Search & Programmatic Tool Calling** (`advanced-tool-use-2025-11-20`): - **[Tool Search Tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool)**: Server-side tool search for efficiently handling large numbers of tools - **[Programmatic Tool Calling (PTC)](https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling)**: Claude writes and executes code to call multiple tools and aggregate results--To enable beta features, use `makeMethodsWith` with the appropriate beta header:--```haskell-let options = defaultClientOptions- { apiKey = key- , anthropicBeta = Just "advanced-tool-use-2025-11-20"- }-let Methods{ createMessage } = makeMethodsWith clientEnv options-```
claude.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: claude-version: 1.3.0+version: 1.3.1 synopsis: Servant bindings to Anthropic's Claude API description: This package provides comprehensive and type-safe bindings to Anthropic's Claude API, providing both a Servant interface
examples/claude-programmatic-tool-calling-example/Main.hs view
@@ -11,7 +11,6 @@ -- aggregates the results. -- -- Requirements:--- * anthropic-beta: advanced-tool-use-2025-11-20 header -- * code_execution_20250825 tool in the tools array -- * Tools must have allowed_callers = ["code_execution_20250825"] module Main where@@ -60,12 +59,9 @@ key <- Text.pack <$> Environment.getEnv "ANTHROPIC_KEY" env <- V1.getClientEnv "https://api.anthropic.com" - -- Create methods with beta header for PTC- 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+ -- PTC is GA, no beta header needed+ let version = Just "2023-06-01"+ let V1.Methods{ V1.createMessage } = V1.makeMethods env key version -- Tools: code execution + query_database with allowed_callers let tools =
examples/claude-tool-search-example/Main.hs view
@@ -8,7 +8,7 @@ -- 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+-- Tool search is generally available (no beta header needed). module Main where import Data.Foldable (toList)@@ -108,12 +108,9 @@ 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+ -- Tool search is GA, no beta header needed+ let version = Just "2023-06-01"+ let V1.Methods{ V1.createMessage } = V1.makeMethods env key version -- Set up tools with tool search enabled -- The tool search tool is NOT deferred, while other tools are deferred
src/Claude/V1.hs view
@@ -112,7 +112,7 @@ , anthropicVersion :: Maybe Text -- ^ Anthropic-Version header (e.g., "2023-06-01") , anthropicBeta :: Maybe Text- -- ^ Anthropic-Beta header for beta features (e.g., "advanced-tool-use-2025-11-20")+ -- ^ Anthropic-Beta header for beta features } deriving stock (Show) -- | Default client options (requires setting apiKey)@@ -143,12 +143,12 @@ -- | Get a record of API methods with full configuration options ----- Use this when you need to pass beta headers (e.g., for tool search):+-- Use this when you need to pass beta headers or other options: -- -- @ -- let options = defaultClientOptions -- { apiKey = key--- , anthropicBeta = Just "advanced-tool-use-2025-11-20"+-- , anthropicBeta = Just "some-beta-feature" -- } -- let Methods{ createMessage } = makeMethodsWith clientEnv options -- @
src/Claude/V1/Tool.hs view
@@ -328,7 +328,6 @@ -- | Code execution tool for programmatic tool calling (PTC) ----- Requires @anthropic-beta: advanced-tool-use-2025-11-20@ header. -- When included in the tools array, Claude can write and execute code -- to call other tools programmatically. codeExecutionTool :: ToolDefinition@@ -357,8 +356,6 @@ allowCallers _ td = td -- | Tool search using regex matching------ Requires @anthropic-beta: advanced-tool-use-2025-11-20@ header. toolSearchRegex :: ToolDefinition toolSearchRegex = ToolDef_SearchTool ToolSearchTool { name = "tool_search_tool_regex"@@ -366,8 +363,6 @@ } -- | Tool search using BM25 matching------ Requires @anthropic-beta: advanced-tool-use-2025-11-20@ header. toolSearchBm25 :: ToolDefinition toolSearchBm25 = ToolDef_SearchTool ToolSearchTool { name = "tool_search_tool_bm25"
tasty/Main.hs view
@@ -519,13 +519,7 @@ then pure () else HUnit.assertFailure $ "Unexpected fast mode failure: " <> errMsg - -- Tool search test requires beta header- let betaOptions = V1.defaultClientOptions- { V1.apiKey = Text.pack key- , V1.anthropicBeta = Just "advanced-tool-use-2025-11-20"- }- let V1.Methods{ V1.createMessage = createMessageBeta } =- V1.makeMethodsWith clientEnv betaOptions+ -- Tool search and PTC are now GA (no beta header needed) let toolSearchTest = HUnit.testCase "Create message - tool search" do@@ -575,7 +569,7 @@ ] Messages.MessageResponse{ stop_reason, content } <-- createMessageBeta+ createMessage Messages._CreateMessage { Messages.model = model , Messages.messages =@@ -605,12 +599,16 @@ HUnit.assertBool "Should have tool_use or server_tool_use content block" hasToolUse' -- Programmatic tool calling test (uses same beta header)+ -- Tests that:+ -- 1. API accepts PTC tool definitions (code_execution + allowed_callers)+ -- 2. Code execution is used (server_tool_use + code_execution_result blocks)+ -- 3. If PTC triggers (stop_reason=Tool_Use), the multi-turn loop works let programmaticToolCallingTest = HUnit.testCase "Create message - programmatic tool calling" do -- Define a simple tool for PTC let queryTool = Tool.functionTool "get_data"- (Just "Get data for a key")+ (Just "Get data for a key. Returns JSON with a numeric 'value' field. You MUST call this tool to get the actual values.") $ Aeson.object [ "properties" Aeson..= Aeson.object [ "key" Aeson..= Aeson.object@@ -638,7 +636,7 @@ -- First request Messages.MessageResponse{ stop_reason, content, container } <-- createMessageBeta+ createMessage Messages._CreateMessage { Messages.model = model , Messages.messages = [initialMessage]@@ -646,87 +644,95 @@ , Messages.tools = Just tools } - -- Should stop for tool use- HUnit.assertEqual "Should stop for tool use"- (Just Messages.Tool_Use)- stop_reason-- -- Should have a server_tool_use for code_execution+ -- Should have a server_tool_use for code_execution (proves API accepted our PTC tools) let hasCodeExecution = any isCodeExecutionServerToolUse (toList content) HUnit.assertBool "Should have server_tool_use for code_execution" hasCodeExecution - -- Should have container info- HUnit.assertBool "Should have container info" (container /= Nothing)+ -- If model triggered PTC (Tool_Use), exercise the multi-turn loop+ -- If model completed code execution without PTC (End_Turn), that's also valid+ case stop_reason of+ Just Messages.Tool_Use -> do+ -- Should have container info+ HUnit.assertBool "Should have container info" (container /= Nothing) - -- Should have tool_use with programmatic caller- let programmaticToolUses =- [ ()- | Messages.ContentBlock_Tool_Use{ Messages.caller = Just (Messages.ToolCaller_CodeExecution{}) }- <- toList content- ]- HUnit.assertBool "Should have at least one programmatic tool call"- (not (null programmaticToolUses))+ -- Should have tool_use with programmatic caller+ let programmaticToolUses =+ [ ()+ | Messages.ContentBlock_Tool_Use{ Messages.caller = Just (Messages.ToolCaller_CodeExecution{}) }+ <- toList content+ ]+ HUnit.assertBool "Should have at least one programmatic tool call"+ (not (null programmaticToolUses)) - -- Multi-turn loop: continue until end_turn or code_execution_tool_result- let containerId = fmap (\Messages.ContainerInfo{ Messages.id = cid } -> cid) container+ -- Multi-turn loop: continue until end_turn or code_execution_tool_result+ let containerId = fmap (\Messages.ContainerInfo{ Messages.id = cid } -> cid) container - -- Build initial assistant message from first response- let assistantContent1 = mapMaybe Messages.contentBlockToContent (toList content)- let assistantMessage1 = Messages.Message- { Messages.role = Messages.Assistant- , Messages.content = Vector.fromList assistantContent1- , Messages.cache_control = Nothing- }- let toolResults1 = processTestToolCalls content- let userMessage1 = Messages.Message- { Messages.role = Messages.User- , Messages.content = Vector.fromList toolResults1- , Messages.cache_control = Nothing- }+ -- Build initial assistant message from first response+ let assistantContent1 = mapMaybe Messages.contentBlockToContent (toList content)+ let assistantMessage1 = Messages.Message+ { Messages.role = Messages.Assistant+ , Messages.content = Vector.fromList assistantContent1+ , Messages.cache_control = Nothing+ }+ let toolResults1 = processTestToolCalls content+ let userMessage1 = Messages.Message+ { Messages.role = Messages.User+ , Messages.content = Vector.fromList toolResults1+ , Messages.cache_control = Nothing+ } - -- Loop function- let loop :: [Messages.Message] -> Maybe Text.Text -> Int -> IO ()- loop _ _ turn | turn > 5 = HUnit.assertFailure "Max turns reached"- loop msgs containerId' turn = do- Messages.MessageResponse{ stop_reason = sr, content = c, container = cont } <-- createMessageBeta- Messages._CreateMessage- { Messages.model = model- , Messages.messages = Vector.fromList msgs- , Messages.max_tokens = 4096- , Messages.tools = Just tools- , Messages.container = containerId'- }+ -- Loop function+ let loop :: [Messages.Message] -> Maybe Text.Text -> Int -> IO ()+ loop _ _ turn | turn > 5 = HUnit.assertFailure "Max turns reached"+ loop msgs containerId' turn = do+ Messages.MessageResponse{ stop_reason = sr, content = c, container = cont } <-+ createMessage+ Messages._CreateMessage+ { Messages.model = model+ , Messages.messages = Vector.fromList msgs+ , Messages.max_tokens = 4096+ , Messages.tools = Just tools+ , Messages.container = containerId'+ } - let newContainerId = case cont of- Just Messages.ContainerInfo{ Messages.id = cid } -> Just cid- Nothing -> containerId'+ let newContainerId = case cont of+ Just Messages.ContainerInfo{ Messages.id = cid } -> Just cid+ Nothing -> containerId' - let hasCodeExecutionResult = any isCodeExecutionResult (toList c)- let isEndTurn = sr == Just Messages.End_Turn+ let hasCodeExecutionResult = any isCodeExecutionResult (toList c)+ let isEndTurn = sr == Just Messages.End_Turn - if hasCodeExecutionResult || isEndTurn- then pure () -- Success!- else if sr == Just Messages.Tool_Use- then do- -- Build next assistant and user messages- let assistantContentN = mapMaybe Messages.contentBlockToContent (toList c)- let assistantMessageN = Messages.Message- { Messages.role = Messages.Assistant- , Messages.content = Vector.fromList assistantContentN- , Messages.cache_control = Nothing- }- let toolResultsN = processTestToolCalls c- let userMessageN = Messages.Message- { Messages.role = Messages.User- , Messages.content = Vector.fromList toolResultsN- , Messages.cache_control = Nothing- }- loop (msgs <> [assistantMessageN, userMessageN]) newContainerId (turn + 1)- else HUnit.assertFailure $ "Unexpected stop_reason: " <> show sr+ if hasCodeExecutionResult || isEndTurn+ then pure () -- Success!+ else if sr == Just Messages.Tool_Use+ then do+ -- Build next assistant and user messages+ let assistantContentN = mapMaybe Messages.contentBlockToContent (toList c)+ let assistantMessageN = Messages.Message+ { Messages.role = Messages.Assistant+ , Messages.content = Vector.fromList assistantContentN+ , Messages.cache_control = Nothing+ }+ let toolResultsN = processTestToolCalls c+ let userMessageN = Messages.Message+ { Messages.role = Messages.User+ , Messages.content = Vector.fromList toolResultsN+ , Messages.cache_control = Nothing+ }+ loop (msgs <> [assistantMessageN, userMessageN]) newContainerId (turn + 1)+ else HUnit.assertFailure $ "Unexpected stop_reason: " <> show sr - -- Start the loop- loop [initialMessage, assistantMessage1, userMessage1] containerId 1+ -- Start the loop+ loop [initialMessage, assistantMessage1, userMessage1] containerId 1++ Just Messages.End_Turn -> do+ -- Model used code execution but solved it without PTC+ -- Still valid: proves our tool definitions were accepted+ let hasResult = any isCodeExecutionResult (toList content)+ HUnit.assertBool "Should have code_execution_result" hasResult++ other ->+ HUnit.assertFailure $ "Unexpected stop_reason: " <> show other let tests = [ messagesMinimalTest