packages feed

langchain-hs-gemini-0.0.6.0: src/Langchain/Embeddings/Gemini.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

{- |
Module      : Langchain.Embeddings.Gemini
Description : Google Gemini integration for text embeddings
Copyright   : (c) 2025-2026 Tushar Adhatrao
License     : MIT
Maintainer  : Tushar Adhatrao <tusharadhatrao@gmail.com>
Stability   : experimental

Gemini implementation of LangChain's 'Embeddings' interface using Servant bindings.
-}
module Langchain.Embeddings.Gemini
  ( GeminiEmbeddings (..)
  , defaultGeminiEmbeddings
  , newGeminiEmbeddings
  ) where

import Control.Monad.Except (throwError)
import Control.Monad.IO.Class (liftIO)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import GHC.Generics (Generic)

import Gemini.Client
  ( BatchEmbedContentsRequest (..)
  , BatchEmbedContentsResponse (..)
  , BatchEmbedRequestItem (..)
  , EmbedContentPayload (..)
  , EmbedContentRequest (..)
  , EmbedContentResponse (..)
  , EmbedPart (..)
  , EmbeddingValues (..)
  , batchEmbedContents
  , embedContent
  , newGeminiClientEnv
  , newGeminiClientEnvWithBaseUrl
  , runGeminiClient
  )
import Langchain.DocumentLoader.Core (Document (..))
import Langchain.Embeddings.Core (Embeddings (..))
import Langchain.Error (llmError)

-- | Configuration for Gemini embeddings.
data GeminiEmbeddings = GeminiEmbeddings
  { embeddingApiKey :: !Text
  -- ^ Gemini API key
  , embeddingModel :: !Text
  -- ^ Model name, e.g. @"gemini-embedding-001"@
  , embeddingBaseUrl :: !(Maybe Text)
  -- ^ Optional custom base URL
  }
  deriving (Eq, Show, Generic)

-- | Default embedding configuration using @gemini-embedding-001@.
defaultGeminiEmbeddings :: Text -> GeminiEmbeddings
defaultGeminiEmbeddings key = GeminiEmbeddings key "gemini-embedding-001" Nothing

-- | Create a custom Gemini embedding configuration.
newGeminiEmbeddings :: Text -> Text -> Maybe Text -> GeminiEmbeddings
newGeminiEmbeddings = GeminiEmbeddings

instance Embeddings GeminiEmbeddings where
  embedDocuments _ [] = pure []
  embedDocuments GeminiEmbeddings {..} docs = do
    env <- liftIO $ case embeddingBaseUrl of
      Just u -> newGeminiClientEnvWithBaseUrl u embeddingApiKey
      Nothing -> newGeminiClientEnv embeddingApiKey
    let items =
          [ BatchEmbedRequestItem
              { itemModel = "models/" <> embeddingModel
              , itemContent = EmbedContentPayload [EmbedPart (TL.toStrict (pageContent doc))]
              }
          | doc <- docs
          ]
        req = BatchEmbedContentsRequest items
    eRes <- liftIO $ runGeminiClient env (batchEmbedContents embeddingApiKey embeddingModel req)
    case eRes of
      Left err ->
        throwError $
          llmError (T.pack (show err)) (Just "GeminiEmbeddings") Nothing
      Right resp ->
        pure [embeddingValues ev | ev <- batchEmbeddings resp]

  embedQuery GeminiEmbeddings {..} query = do
    env <- liftIO $ case embeddingBaseUrl of
      Just u -> newGeminiClientEnvWithBaseUrl u embeddingApiKey
      Nothing -> newGeminiClientEnv embeddingApiKey
    let req = EmbedContentRequest (EmbedContentPayload [EmbedPart query])
    eRes <- liftIO $ runGeminiClient env (embedContent embeddingApiKey embeddingModel req)
    case eRes of
      Left err ->
        throwError $
          llmError (T.pack (show err)) (Just "GeminiEmbeddings") Nothing
      Right resp ->
        pure $ embeddingValues (responseEmbedding resp)