packages feed

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 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