packages feed

llama-cpp-hs-0.1.0.0: src/Llama/ChatTemplate.hs

{- |
Module      : Llama.ChatTemplate
Description : Chat template related functions for llama-cpp
Copyright   : (c) 2025 Tushar Adhatrao
License     : MIT
Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>
-}
module Llama.ChatTemplate (
    ChatMessage (..)
    , toCLlamaChatMessage
   , chatApplyTemplate
   , chatGetBuiltinTemplates
  ) where

import Data.Functor
import Foreign
import Foreign.C.String
import Llama.Internal.Foreign
import Llama.Internal.Types

data ChatMessage = ChatMessage
  { chatRole :: String
  , chatContent :: String
  }
  deriving (Show, Eq)

toCLlamaChatMessage :: ChatMessage -> IO LlamaChatMessage
toCLlamaChatMessage msg = do
  rolePtr <- newCString (chatRole msg)
  contentPtr <- newCString (chatContent msg)
  return $ LlamaChatMessage rolePtr contentPtr

-- | Apply a chat template to format a conversation.
chatApplyTemplate ::
  -- | Optional custom template (uses built-in if Nothing)
  Maybe String ->
  -- | List of chat messages
  [ChatMessage] ->
  -- | Add assistant token at end?
  Bool ->
  -- | Buffer size (suggested: 4096)
  Int ->
  -- | Returns formatted string or error message
  IO (Either String String)
chatApplyTemplate mTemplate messages addAssist bufferSize = do
  let nMessages = length messages
      bufSize = max bufferSize (nMessages * 64) -- Heuristic fallback
  templateCString <- maybe (return nullPtr) newCString mTemplate
  msgs <- mapM toCLlamaChatMessage messages
  cMessages <- withArray msgs $ \ptr ->
    return ptr

  allocaBytes bufSize $ \bufPtr -> do
    result <-
      c_llama_chat_apply_template
        templateCString
        cMessages
        (fromIntegral nMessages)
        (if addAssist then 1 else 0)
        bufPtr
        (fromIntegral bufSize)

    if result < 0
      then return $ Left "Failed to apply chat template"
      else do
        let actualSize = fromIntegral result
        if actualSize >= bufSize
          then -- Need larger buffer
            allocaBytes actualSize $ \newBufPtr -> do
              result' <-
                c_llama_chat_apply_template
                  templateCString
                  cMessages
                  (fromIntegral nMessages)
                  (if addAssist then 1 else 0)
                  newBufPtr
                  (fromIntegral actualSize)
              if result' < 0
                then return $ Left "Failed to apply chat template after resize"
                else peekCString newBufPtr <&> Right
          else do
            str <- peekCString bufPtr
            return $ Right str

-- | Get list of available built-in chat templates.
chatGetBuiltinTemplates :: IO [String]
chatGetBuiltinTemplates = do
  -- First get number of templates
  numTemplates <- c_llama_chat_builtin_templates nullPtr 0
  let maxTemplates = fromIntegral numTemplates

  if numTemplates <= 0
    then return []
    else do
      -- Allocate array of CString pointers
      arrPtr <- mallocArray maxTemplates
      result <- c_llama_chat_builtin_templates arrPtr (fromIntegral maxTemplates)
      if result < 0
        then return []
        else do
          cstrs <- peekArray maxTemplates arrPtr
          strs <- mapM peekCString cstrs
          free arrPtr
          return strs