packages feed

langchain-hs-0.0.3.0: src/Langchain/LLM/Huggingface.hs

{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}

{- |
Module:      Langchain.LLM.Huggingface
Copyright:   (c) 2025 Tushar Adhatrao
License:     MIT
Maintainer:  Tushar Adhatrao <tusharadhatrao@gmail.com>
Stability:   experimental

Huggingface inference implementation Langchain's LLM Interface.
https://huggingface.co/docs/inference-providers/providers/cerebras

* Support for text generation, chat, and streaming responses
* Configuration of Huggingface-specific parameters (temperature, max tokens, etc.)
* Conversion between Langchain's message format and Huggingface's API requirements
* Compatibility with Huggingface's hosted inference API and other providers
-}
module Langchain.LLM.Huggingface
  ( -- * Types
    Huggingface (..)
  , Huggingface.Provider (..)
  , HuggingfaceParams (..)

    -- * Functions
  , defaultHuggingfaceParams
  , Huggingface.defaultHugginfaceMessage

    -- * Re-export
  , module LLM
  ) where

import qualified Data.List.NonEmpty as NE
import Data.Maybe
import Data.Text (Text, unpack)
import qualified Data.Text as T
import Langchain.Callback
import Langchain.Error (llmError)
import Langchain.LLM.Core as LLM
import qualified Langchain.LLM.Internal.Huggingface as Huggingface

-- | Configuration for Huggingface LLM integration
data Huggingface = Huggingface
  { provider :: Huggingface.Provider
  -- ^ Service provider (e.g., HostedInferenceAPI)
  , apiKey :: Text
  -- ^ Huggingface API authentication key
  , modelName :: Text
  -- ^ Model identifier (e.g., "google/flan-t5-xl")
  , callbacks :: [Callback]
  -- ^ Event handlers for inference lifecycle
  }

instance Show Huggingface where
  show Huggingface {..} =
    "Huggingface { provider = "
      <> show provider
      <> ", modelName = "
      <> unpack modelName
      <> " }"

-- | Generation parameters specific to Huggingface models
data HuggingfaceParams = HuggingfaceParams
  { frequencyPenalty :: Maybe Double
  -- ^ Penalty for token frequency (0.0-2.0)
  , maxTokens :: Maybe Integer
  -- ^ Token limit for output
  , presencePenalty :: Maybe Double
  -- ^ Penalty for token presence (0.0-2.0)
  , stop :: Maybe [String]
  -- ^ Stop sequences to terminate generation
  , toolPrompt :: Maybe String
  -- ^ Special prompt for tool interactions
  , topP :: Maybe Double
  -- ^ Nucleus sampling probability threshold
  , temperature :: Maybe Double
  -- ^ Sampling temperature (0.0-1.0)
  , timeout :: Maybe Int
  -- ^ Number of seconds for request timeout
  }
  deriving (Eq, Show)

-- | Default values for huggingface params
defaultHuggingfaceParams :: HuggingfaceParams
defaultHuggingfaceParams =
  HuggingfaceParams
    { frequencyPenalty = Nothing
    , maxTokens = Nothing
    , presencePenalty = Nothing
    , stop = Nothing
    , toolPrompt = Nothing
    , topP = Nothing
    , temperature = Nothing
    , timeout = Just 60
    }

instance LLM Huggingface where
  type LLMParams Huggingface = HuggingfaceParams
  type LLMStreamTokenType Huggingface = Text

  generate Huggingface {..} prompt mbHuggingfaceParams = do
    eRes <-
      Huggingface.createChatCompletion
        apiKey
        Huggingface.defaultHuggingfaceChatCompletionRequest
          { Huggingface.provider = provider
          , Huggingface.messages =
              [ Huggingface.defaultHugginfaceMessage
                  { Huggingface.content = Huggingface.TextContent prompt
                  }
              ]
          , Huggingface.model = modelName
          , Huggingface.stream = False
          , Huggingface.maxTokens = maxTokens =<< mbHuggingfaceParams
          , Huggingface.frequencyPenalty = frequencyPenalty =<< mbHuggingfaceParams
          , -- , Huggingface.logProbs = maybe Nothing logProbs mbHuggingfaceParams
            Huggingface.presencePenalty = presencePenalty =<< mbHuggingfaceParams
          , -- , Huggingface.seed = maybe Nothing seed mbHuggingfaceParams
            Huggingface.stop = stop =<< mbHuggingfaceParams
          , Huggingface.temperature = temperature =<< mbHuggingfaceParams
          , -- , Huggingface.toolPrompt = maybe Nothing toolPrompt mbHuggingfaceParams
            -- , Huggingface.topLogprobs = maybe Nothing topLogProbs mbHuggingfaceParams
            Huggingface.topP = topP =<< mbHuggingfaceParams
          , Huggingface.timeout = timeout =<< mbHuggingfaceParams
          -- , Huggingface.streamOptions = maybe Nothing streamOptions mbHuggingfaceParams
          -- , Huggingface.responseFormat = maybe Nothing responseFormat mbHuggingfaceParams
          -- , Huggingface.tools = maybe Nothing tools mbHuggingfaceParams
          -- , Huggingface.toolChoice = maybe Nothing toolChoice mbHuggingfaceParams
          }
    case eRes of
      Left err -> return $ Left (llmError (T.pack err) Nothing Nothing)
      Right r -> do
        case listToMaybe ((\Huggingface.ChatCompletionResponse {..} -> choices) r) of
          Nothing ->
            return $
              Left
                (llmError "Did not received any response" Nothing Nothing)
          Just resp ->
            let Huggingface.Message {..} = Huggingface.message resp
             in pure $
                  Right $
                    ( \case
                        Huggingface.TextContent t -> t
                        _ -> ""
                    )
                      content

  chat Huggingface {..} msgs mbHuggingfaceParams = do
    eRes <-
      Huggingface.createChatCompletion
        apiKey
        Huggingface.defaultHuggingfaceChatCompletionRequest
          { Huggingface.provider = provider
          , Huggingface.messages = toHuggingfaceMessages msgs
          , Huggingface.model = modelName
          , Huggingface.stream = False
          , Huggingface.maxTokens = maxTokens =<< mbHuggingfaceParams
          , Huggingface.frequencyPenalty = frequencyPenalty =<< mbHuggingfaceParams
          , -- , Huggingface.logProbs = maybe Nothing logProbs mbHuggingfaceParams
            Huggingface.presencePenalty = presencePenalty =<< mbHuggingfaceParams
          , -- , Huggingface.seed = maybe Nothing seed mbHuggingfaceParams
            Huggingface.stop = stop =<< mbHuggingfaceParams
          , Huggingface.temperature = temperature =<< mbHuggingfaceParams
          , -- , Huggingface.toolPrompt = maybe Nothing toolPrompt mbHuggingfaceParams
            -- , Huggingface.topLogprobs = maybe Nothing topLogProbs mbHuggingfaceParams
            Huggingface.topP = topP =<< mbHuggingfaceParams
          , Huggingface.timeout = timeout =<< mbHuggingfaceParams
          -- , Huggingface.streamOptions = maybe Nothing streamOptions mbHuggingfaceParams
          -- , Huggingface.responseFormat = maybe Nothing responseFormat mbHuggingfaceParams
          -- , Huggingface.tools = maybe Nothing tools mbHuggingfaceParams
          -- , Huggingface.toolChoice = maybe Nothing toolChoice mbHuggingfaceParams
          }
    case eRes of
      Left err -> return $ Left $ llmError (T.pack err) Nothing Nothing
      Right r -> do
        case listToMaybe
          ((\Huggingface.ChatCompletionResponse {..} -> choices) r) of
          Nothing ->
            return $
              Left (llmError "Did not received any response" Nothing Nothing)
          Just resp -> return $ Right $ from (Huggingface.message resp)

  stream Huggingface {..} msgs LLM.StreamHandler {..} mbHuggingfaceParams = do
    eRes <-
      Huggingface.createChatCompletionStream
        apiKey
        Huggingface.defaultHuggingfaceChatCompletionRequest
          { Huggingface.provider = provider
          , Huggingface.messages = toHuggingfaceMessages msgs
          , Huggingface.model = modelName
          , Huggingface.stream = True
          , Huggingface.maxTokens = maxTokens =<< mbHuggingfaceParams
          , Huggingface.frequencyPenalty = frequencyPenalty =<< mbHuggingfaceParams
          , -- , Huggingface.logProbs = maybe Nothing logProbs mbHuggingfaceParams
            Huggingface.presencePenalty = presencePenalty =<< mbHuggingfaceParams
          , -- , Huggingface.seed = maybe Nothing seed mbHuggingfaceParams
            Huggingface.stop = stop =<< mbHuggingfaceParams
          , Huggingface.temperature = temperature =<< mbHuggingfaceParams
          , -- , Huggingface.toolPrompt = maybe Nothing toolPrompt mbHuggingfaceParams
            -- , Huggingface.topLogprobs = maybe Nothing topLogProbs mbHuggingfaceParams
            Huggingface.topP = topP =<< mbHuggingfaceParams
          , Huggingface.timeout = timeout =<< mbHuggingfaceParams
          -- , Huggingface.streamOptions = maybe Nothing streamOptions mbHuggingfaceParams
          -- , Huggingface.responseFormat = maybe Nothing responseFormat mbHuggingfaceParams
          -- , Huggingface.tools = maybe Nothing tools mbHuggingfaceParams
          -- , Huggingface.toolChoice = maybe Nothing toolChoice mbHuggingfaceParams
          }
        Huggingface.HuggingfaceStreamHandler
          { Huggingface.onComplete = onComplete
          , Huggingface.onToken = onToken . chunkToText
          }
    case eRes of
      Left err -> pure $ Left $ llmError (T.pack err) Nothing Nothing
      Right r -> pure $ Right r
    where
      chunkToText :: Huggingface.ChatCompletionChunk -> Text
      chunkToText Huggingface.ChatCompletionChunk {..} = do
        case listToMaybe chunkChoices of
          Nothing -> ""
          Just Huggingface.ChoiceChunk {..} ->
            fromMaybe "" ((\Huggingface.Delta {..} -> deltaContent) delta)

toHuggingfaceMessages :: LLM.ChatHistory -> [Huggingface.Message]
toHuggingfaceMessages msgs = map go (NE.toList msgs)
  where
    toRole :: LLM.Role -> Huggingface.Role
    toRole r = case r of
      LLM.System -> Huggingface.System
      LLM.User -> Huggingface.User
      LLM.Assistant -> Huggingface.Assistant
      LLM.Tool -> Huggingface.Tool
      _ -> Huggingface.System
    -- LLM.Developer -> Huggingface.Developer
    -- LLM.Function -> Huggingface.Function

    go :: LLM.Message -> Huggingface.Message
    go msg =
      Huggingface.defaultHugginfaceMessage
        { Huggingface.role = toRole $ LLM.role msg
        , Huggingface.content = Huggingface.TextContent (LLM.content msg)
        }