packages feed

langchain-hs-0.0.5.0: src/Langchain/Tool/GenericSchema.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}

{- |
Module      : Langchain.Tool.GenericSchema
Description : Type-safe tool parameter JSON schema derivation using GHC Generics
Copyright   : (c) 2025-2026 Tushar Adhatrao
License     : MIT
Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>
Stability   : experimental

Automatically derives OpenAI-compatible tool JSON schema objects from Haskell record types
using GHC Generics at compile time.
-}
module Langchain.Tool.GenericSchema
  ( DeriveToolSchema (..)
  , deriveToolParametersSchema
  ) where

import Data.Aeson (Value (..), object, (.=))
import qualified Data.Aeson.Key as Key
import Data.Int (Int16, Int32, Int64, Int8)
import Data.Kind (Type)
import qualified Data.Map.Strict as Map
import Data.Proxy (Proxy (..))
import Data.Scientific (Scientific)
import Data.Text (Text)
import qualified Data.Text as TS
import Data.Time (Day, UTCTime)
import Data.Word (Word16, Word32, Word64, Word8)
import GHC.Generics

-- | Typeclass for deriving tool JSON schema parameters
class DeriveToolSchema a where
  deriveToolSchema :: Proxy a -> Value
  default deriveToolSchema :: (GToolRecordSchema (Rep a)) => Proxy a -> Value
  deriveToolSchema _ = deriveToolParametersSchema (Proxy :: Proxy a)

-- | Derive OpenAI tool parameter schema object
deriveToolParametersSchema ::
  forall a. (GToolRecordSchema (Rep a)) => Proxy a -> Value
deriveToolParametersSchema _ =
  let (props, reqs) = gToolRecordSchema (Proxy :: Proxy (Rep a))
   in object
        [ "type" .= ("object" :: Text)
        , "properties" .= object props
        , "required" .= reqs
        ]

class GToolRecordSchema (f :: Type -> Type) where
  gToolRecordSchema :: Proxy f -> ([(Key.Key, Value)], [Text])

instance (GToolRecordSchema f, GToolRecordSchema g) => GToolRecordSchema (f :*: g) where
  gToolRecordSchema _ =
    let (p1, r1) = gToolRecordSchema (Proxy :: Proxy f)
        (p2, r2) = gToolRecordSchema (Proxy :: Proxy g)
     in (p1 ++ p2, r1 ++ r2)

instance (GToolRecordSchema f) => GToolRecordSchema (M1 D c f) where
  gToolRecordSchema _ = gToolRecordSchema (Proxy :: Proxy f)

instance (GToolRecordSchema f) => GToolRecordSchema (M1 C c f) where
  gToolRecordSchema _ = gToolRecordSchema (Proxy :: Proxy f)

instance (Selector s, ToolFieldSchema a) => GToolRecordSchema (M1 S s (K1 R a)) where
  gToolRecordSchema _ =
    let selNameStr = selName (undefined :: M1 S s (K1 R a) p)
        propKey = Key.fromString selNameStr
        propSchema = toolFieldSchema (Proxy :: Proxy a)
        req = [TS.pack selNameStr | not (isOptionalField (Proxy :: Proxy a))]
     in ([(propKey, propSchema)], req)

class ToolFieldSchema a where
  toolFieldSchema :: Proxy a -> Value
  default toolFieldSchema :: (GToolRecordSchema (Rep a)) => Proxy a -> Value
  toolFieldSchema _ = deriveToolParametersSchema (Proxy :: Proxy a)

  isOptionalField :: Proxy a -> Bool
  isOptionalField _ = False

instance ToolFieldSchema Text where
  toolFieldSchema _ = object ["type" .= ("string" :: Text)]

instance ToolFieldSchema String where
  toolFieldSchema _ = object ["type" .= ("string" :: Text)]

instance ToolFieldSchema Char where
  toolFieldSchema _ = object ["type" .= ("string" :: Text)]

instance ToolFieldSchema Int where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Int8 where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Int16 where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Int32 where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Int64 where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Integer where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Word where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Word8 where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Word16 where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Word32 where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Word64 where
  toolFieldSchema _ = object ["type" .= ("integer" :: Text)]

instance ToolFieldSchema Double where
  toolFieldSchema _ = object ["type" .= ("number" :: Text)]

instance ToolFieldSchema Float where
  toolFieldSchema _ = object ["type" .= ("number" :: Text)]

instance ToolFieldSchema Scientific where
  toolFieldSchema _ = object ["type" .= ("number" :: Text)]

instance ToolFieldSchema Bool where
  toolFieldSchema _ = object ["type" .= ("boolean" :: Text)]

instance ToolFieldSchema UTCTime where
  toolFieldSchema _ =
    object
      [ "type" .= ("string" :: Text)
      , "format" .= ("date-time" :: Text)
      ]

instance ToolFieldSchema Day where
  toolFieldSchema _ =
    object
      [ "type" .= ("string" :: Text)
      , "format" .= ("date" :: Text)
      ]

instance ToolFieldSchema Value where
  toolFieldSchema _ = object ["type" .= ("object" :: Text)]

instance (ToolFieldSchema a) => ToolFieldSchema (Map.Map Text a) where
  toolFieldSchema _ =
    object
      [ "type" .= ("object" :: Text)
      , "additionalProperties" .= toolFieldSchema (Proxy :: Proxy a)
      ]

instance (ToolFieldSchema a) => ToolFieldSchema (Maybe a) where
  toolFieldSchema _ = toolFieldSchema (Proxy :: Proxy a)
  isOptionalField _ = True

instance {-# OVERLAPPABLE #-} (ToolFieldSchema a) => ToolFieldSchema [a] where
  toolFieldSchema _ =
    object
      [ "type" .= ("array" :: Text)
      , "items" .= toolFieldSchema (Proxy :: Proxy a)
      ]