baikai-claude-0.3.0.0: src/Baikai/Provider/Claude/Shape.hs
{-# LANGUAGE LambdaCase #-}
-- | Pure request-body shaping for Anthropic-compatible Messages hosts.
module Baikai.Provider.Claude.Shape
( shapeRequestBody,
streamRequestBody,
replaceToolSchemas,
injectToolChoiceNone,
injectToolCacheControl,
)
where
import Baikai.CacheRetention (CacheRetention (..))
import Baikai.Compat (AnthropicMessagesCompat (supportsCacheControlOnTools, supportsLongCacheRetention))
import Baikai.Context (Context, tools)
import Baikai.Options (Options, cacheRetention, toolChoice)
import Baikai.Tool qualified as Tool
import Claude.V1.Messages qualified as Messages
import Data.Aeson (Value (..), (.=))
import Data.Aeson qualified as Aeson
import Data.Aeson.Key qualified as AesonKey
import Data.Aeson.KeyMap (KeyMap)
import Data.Aeson.KeyMap qualified as KeyMap
import Data.Text (Text)
import Data.Vector qualified as Vector
shapeRequestBody ::
AnthropicMessagesCompat ->
Context ->
Options ->
Aeson.Value ->
Aeson.Value
shapeRequestBody compat ctx opts =
injectToolCacheControl compat opts
. injectToolChoiceNone opts
. replaceToolSchemas ctx
streamRequestBody ::
AnthropicMessagesCompat ->
Context ->
Options ->
Messages.CreateMessage ->
Aeson.Value
streamRequestBody compat ctx opts req =
shapeRequestBody compat ctx opts (Aeson.toJSON req {Messages.stream = Just True})
replaceToolSchemas :: Context -> Aeson.Value -> Aeson.Value
replaceToolSchemas ctx
| Vector.null toolsVec = id
| otherwise =
mapObject $
adjustKey (key "tools") $
mapTools (Vector.toList toolsVec)
where
toolsVec = tools ctx
injectToolChoiceNone :: Options -> Aeson.Value -> Aeson.Value
injectToolChoiceNone opts
| Just Tool.ToolChoiceNone <- toolChoice opts =
mapObject (KeyMap.insert (key "tool_choice") (Aeson.object ["type" .= ("none" :: Text)]))
| otherwise = id
injectToolCacheControl :: AnthropicMessagesCompat -> Options -> Aeson.Value -> Aeson.Value
injectToolCacheControl compat opts body =
case (supportsCacheControlOnTools compat, cacheRetention opts) of
(True, Just retention)
| Just marker <- cacheControlMarker compat retention ->
mapObject (adjustKey (key "tools") (markLastTool marker)) body
_ -> body
mapTools :: [Tool.Tool] -> Aeson.Value -> Aeson.Value
mapTools toolDefs = \case
Array toolsJson ->
Array (Vector.imap patchTool toolsJson)
other -> other
where
patchTool i =
case drop i toolDefs of
t : _ -> replaceInputSchema (Tool.parameters t)
[] -> id
replaceInputSchema :: Aeson.Value -> Aeson.Value -> Aeson.Value
replaceInputSchema schema =
mapObject (KeyMap.insert (key "input_schema") schema)
markLastTool :: Aeson.Value -> Aeson.Value -> Aeson.Value
markLastTool marker = \case
Array toolsJson
| not (Vector.null toolsJson) ->
let i = Vector.length toolsJson - 1
in Array (toolsJson Vector.// [(i, markTool marker (toolsJson Vector.! i))])
other -> other
markTool :: Aeson.Value -> Aeson.Value -> Aeson.Value
markTool marker =
mapObject (KeyMap.insert (key "cache_control") marker)
cacheControlMarker :: AnthropicMessagesCompat -> CacheRetention -> Maybe Aeson.Value
cacheControlMarker compat = \case
CacheRetentionNone -> Nothing
CacheRetentionShort -> Just (Aeson.object ["type" .= ("ephemeral" :: Text)])
CacheRetentionLong ->
if supportsLongCacheRetention compat
then
Just
( Aeson.object
[ "type" .= ("ephemeral" :: Text),
"ttl" .= ("1h" :: Text)
]
)
else Just (Aeson.object ["type" .= ("ephemeral" :: Text)])
mapObject :: (KeyMap Aeson.Value -> KeyMap Aeson.Value) -> Aeson.Value -> Aeson.Value
mapObject f = \case
Object obj -> Object (f obj)
other -> other
adjustKey ::
AesonKey.Key ->
(Aeson.Value -> Aeson.Value) ->
KeyMap Aeson.Value ->
KeyMap Aeson.Value
adjustKey k f obj =
case KeyMap.lookup k obj of
Nothing -> obj
Just old -> KeyMap.insert k (f old) obj
key :: Text -> AesonKey.Key
key = AesonKey.fromText