packages feed

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

{-# 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.defaultMessage
  ) where

import qualified Data.List.NonEmpty as NE
import Data.Maybe
import Data.Text (Text, unpack)
import Langchain.Callback
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

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

  stream Huggingface {..} msgs LLM.StreamHandler {..} mbHuggingfaceParams = do
    Huggingface.createChatCompletionStream
      apiKey
      Huggingface.defaultHuggingfaceChatCompletionRequest
        { Huggingface.provider = provider
        , Huggingface.messages = toHuggingfaceMessages msgs
        , Huggingface.model = modelName
        , Huggingface.stream = True
        , Huggingface.maxTokens = maybe Nothing maxTokens mbHuggingfaceParams
        , Huggingface.frequencyPenalty = maybe Nothing frequencyPenalty mbHuggingfaceParams
        , -- , Huggingface.logProbs = maybe Nothing logProbs mbHuggingfaceParams
          Huggingface.presencePenalty = maybe Nothing presencePenalty mbHuggingfaceParams
        , -- , Huggingface.seed = maybe Nothing seed mbHuggingfaceParams
          Huggingface.stop = maybe Nothing stop mbHuggingfaceParams
        , Huggingface.temperature = maybe Nothing temperature mbHuggingfaceParams
        , -- , Huggingface.toolPrompt = maybe Nothing toolPrompt mbHuggingfaceParams
          -- , Huggingface.topLogprobs = maybe Nothing topLogProbs mbHuggingfaceParams
          Huggingface.topP = maybe Nothing topP mbHuggingfaceParams
        , Huggingface.timeout = maybe Nothing 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
        }
    where
      chunkToText :: Huggingface.ChatCompletionChunk -> Text
      chunkToText Huggingface.ChatCompletionChunk {..} = do
        case listToMaybe chunkChoices of
          Nothing -> ""
          Just Huggingface.ChoiceChunk {..} ->
            fromMaybe "" ((\Huggingface.Delta {..} -> deltaContent) delta)

toHuggingfaceMessages :: LLM.ChatMessage -> [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.defaultMessage
        { Huggingface.role = toRole $ LLM.role msg
        , Huggingface.content = Huggingface.TextContent (LLM.content msg)
        }