packages feed

pinecone-1.0.0: src/Pinecone.hs

-- | Main entrypoint to the API
module Pinecone
    ( -- * Methods
      getClientEnv
    , makeControlMethods
    , ControlMethods(..)
    , makeDataMethods
    , DataMethods(..)

      -- * Servant
    , ControlAPI
    , DataAPI
    ) where

import Data.Foldable (toList)
import Data.Functor (void)
import Data.Proxy (Proxy(..))
import Pinecone.Embed (GenerateVectors, Embeddings)
import Pinecone.Prelude
import Pinecone.Rerank (Documents(..), RerankResults(..))
import Prelude hiding (id)
import Servant.Client (ClientEnv)
import Servant.Client.Core (BaseUrl(..), Scheme(..))

import Pinecone.Backups
    (CreateCollection, Collection, CollectionModel, Collections(..))
import Pinecone.Imports
    (Import, ImportModel, Imports, StartImport, StartImportResponse(..))
import Pinecone.Indexes
    ( ConfigureIndex
    , CreateIndex
    , CreateIndexWithEmbedding
    , GetIndexStats
    , Host(..)
    , Index
    , IndexModel
    , IndexModels(..)
    , IndexStats
    )
import Pinecone.Search
    ( Hits
    , Matches
    , SearchWithText
    , SearchWithVector
    )
import Pinecone.Vectors
    ( DeleteVectors
    , Namespace
    , Record
    , UpdateVector
    , UpsertVectors
    , UpsertStats
    , VectorIDs
    , Vectors
    )

import qualified Control.Exception as Exception
import qualified Data.Text as Text
import qualified Pinecone.Backups as Backups
import qualified Pinecone.Embed as Embed
import qualified Pinecone.Imports as Imports
import qualified Pinecone.Indexes as Indexes
import qualified Pinecone.Rerank as Rerank
import qualified Pinecone.Search as Search
import qualified Pinecone.Vectors as Vectors
import qualified Network.HTTP.Client as HTTP.Client
import qualified Network.HTTP.Client.TLS as TLS
import qualified Servant.Client as Client

-- | Convenient utility to get a `ClientEnv` for the most common use case
getClientEnv
    :: Host
    -- ^ Base URL for API
    --
    -- __CAREFULLY NOTE:__ This should be @\"app.pinecone.io\"@ for
    -- `makeControlMethods` and should be /the index host/ for `makeDataMethods`
    -- (i.e. the `Pinecone.Indexes.host` field of a returned `IndexModel`).
    -> IO ClientEnv
getClientEnv (Host baseUrlText) = do
    baseUrl <- Client.parseBaseUrl (Text.unpack baseUrlText)

    -- The hosts returned by the Pinecone API don't include the `https://`
    -- prefix, which means that by default they're parsed as insecure URLs,
    -- which we fix here.
    --
    -- If you don't like this behavior or you specifically want to use an
    -- insecure URL then just use the `mkClientEnv` function yourself instead
    -- of using this `getClientEnv` utility function.
    let newBaseUrl = baseUrl
          { baseUrlScheme = Https
          , baseUrlPort = 443
          }

    let managerSettings = TLS.tlsManagerSettings
            { HTTP.Client.managerResponseTimeout =
                HTTP.Client.responseTimeoutNone
            }

    manager <- TLS.newTlsManagerWith managerSettings

    pure (Client.mkClientEnv manager newBaseUrl)

apiVersion :: Text
apiVersion = "2025-01"

-- | Get a record of control API methods after providing an API token
makeControlMethods
    :: ClientEnv
    -- ^
    -> Text
    -- ^ API token
    -> ControlMethods
makeControlMethods clientEnv token = ControlMethods{..}
  where
    (       (     listIndexes_
            :<|>  createIndex
            :<|>  createIndexWithEmbedding
            :<|>  describeIndex
            :<|>  deleteIndex_
            :<|>  configureIndex
            )
      :<|>  (     listCollections_
            :<|>  createCollection
            :<|>  describeCollection
            :<|>  deleteCollection_
            )
      :<|>  generateVectors
      :<|>  rerankResults
      ) = Client.hoistClient @ControlAPI Proxy (run clientEnv) (Client.client @ControlAPI Proxy) token apiVersion

    listIndexes = fmap indexes listIndexes_
    listCollections = fmap collections listCollections_
    deleteIndex a = void (deleteIndex_ a)
    deleteCollection a = void (deleteCollection_ a)

-- | Get a record of control API methods after providing an API token
makeDataMethods
    :: ClientEnv
    -- ^
    -> Text
    -- ^ API token
    -> DataMethods
makeDataMethods clientEnv token = DataMethods{..}
  where
    (      getIndexStats
      :<|> (      (     upsertVectors
                  :<|>  fetchVectors_
                  :<|>  updateVector_
                  :<|>  deleteVectors_
                  :<|>  listVectorIDs
                  )
            :<|>  upsertText_
            )
      :<|>  (     searchWithVector
            :<|>  searchWithText
            )
      :<|>  (     startImport_
            :<|>  listImports
            :<|>  describeImport
            :<|>  cancelImport_
            )
      ) = Client.hoistClient @DataAPI Proxy (run clientEnv) (Client.client @DataAPI Proxy) token apiVersion

    fetchVectors a = fetchVectors_ (toList a)
    updateVector a = void (updateVector_ a)
    deleteVectors a = void (deleteVectors_ a)
    upsertText a b = void (upsertText_ a b)
    startImport a = do StartImportResponse{ id } <- startImport_ a; return id
    cancelImport a = void (cancelImport_ a)

run :: Client.ClientEnv -> Client.ClientM a -> IO a
run clientEnv clientM = do
    result <- Client.runClientM clientM clientEnv
    case result of
        Left exception -> Exception.throwIO exception
        Right a -> return a

-- | Control plane methods
data ControlMethods = ControlMethods
    { listIndexes :: IO (Vector IndexModel)
    , createIndex :: CreateIndex -> IO IndexModel
    , createIndexWithEmbedding :: CreateIndexWithEmbedding -> IO IndexModel
    , describeIndex :: Index -> IO IndexModel
    , deleteIndex :: Index -> IO ()
    , configureIndex :: Index -> ConfigureIndex -> IO IndexModel
    , listCollections :: IO (Vector CollectionModel)
    , createCollection :: CreateCollection -> IO CollectionModel
    , describeCollection :: Collection -> IO CollectionModel
    , deleteCollection :: Collection -> IO ()
    , generateVectors :: GenerateVectors -> IO Embeddings
    , rerankResults :: RerankResults -> IO Documents
    }

-- | Data plane methods
data DataMethods = DataMethods
    { getIndexStats :: GetIndexStats -> IO IndexStats
    , upsertVectors :: UpsertVectors -> IO UpsertStats
    , upsertText :: Namespace -> Record -> IO ()
    , fetchVectors
        :: Vector Text
        -- ^ IDs
        -> Maybe Namespace
        -- ^ namespace
        -> IO Vectors
    , updateVector :: UpdateVector -> IO ()
    , deleteVectors :: DeleteVectors -> IO ()
    , listVectorIDs
        :: Maybe Text
        -- ^ prefix
        -> Maybe Natural
        -- ^ limit
        -> Maybe Text
        -- ^ pagination token
        -> Maybe Namespace
        -- ^ namespace
        -> IO VectorIDs
    , searchWithVector :: SearchWithVector -> IO Matches
    , searchWithText :: Namespace -> SearchWithText -> IO Hits
    , startImport :: StartImport -> IO Import
    , listImports
        :: Maybe Natural
        -- ^ limit
        -> Maybe Text
        -- ^ paginationToken
        -> IO Imports
    , describeImport :: Import -> IO ImportModel
    , cancelImport :: Import -> IO ()
    }

-- | Index management
type ControlAPI =
        Header' [ Required, Strict ] "Api-Key" Text
    :>  Header' [ Required, Strict ] "X-Pinecone-API-Version" Text
    :>  (Indexes.ControlAPI :<|> Backups.API :<|> Embed.API :<|> Rerank.API)

-- | Index operations
type DataAPI =
        Header' [ Required, Strict ] "Api-Key" Text
    :>  Header' [ Required, Strict ] "X-Pinecone-API-Version" Text
    :>  (Indexes.DataAPI :<|> Vectors.API :<|> Search.API :<|> Imports.API)