packages feed

openai-hs-0.3.0.1: test/ApiSpec.hs

module ApiSpec (apiSpec) where

import qualified Data.Text as T
import qualified Data.Vector as V
import Network.HTTP.Client
import Network.HTTP.Client.TLS
import OpenAI.Client
import System.Environment (getEnv)
import Test.Hspec

makeClient :: IO OpenAIClient
makeClient =
  do
    manager <- newManager tlsManagerSettings
    apiKey <- T.pack <$> getEnv "OPENAI_KEY"
    pure (makeOpenAIClient apiKey manager 2)

forceSuccess :: (MonadFail m, Show a) => m (Either a b) -> m b
forceSuccess req =
  req >>= \res ->
    case res of
      Left err -> fail (show err)
      Right ok -> pure ok

apiSpec :: Spec
apiSpec = do
  describe "2022 core api" apiTests2022
  describe "March 2023 core API" apiTests2023

---------------------------------
------- 2023 API tests ----------
---------------------------------

apiTests2023 :: SpecWith ()
apiTests2023 =
  beforeAll makeClient $ do
    describe "models api" $ do
      it "list models" $ \cli -> do
        res <- forceSuccess $ listModels cli
        (V.length (olData res) > 5) `shouldBe` True
        let model = V.head (olData res)
        mOwnedBy model `shouldBe` "openai-internal"

      it "retrieve model" $ \cli -> do
        model <- forceSuccess $ getModel cli (ModelId "text-davinci-003")
        mOwnedBy model `shouldBe` "openai-internal"

    describe "completions api" $ do
      it "create completion" $ \cli -> do
        let completion =
              (defaultCompletionCreate (ModelId "text-ada-001") "The opposite of up is")
                { ccrMaxTokens = Just 1,
                  ccrTemperature = Just 0.1,
                  ccrN = Just 1
                }
        res <- forceSuccess $ completeText cli completion
        crChoices res `shouldNotBe` []
        cchText (head (crChoices res)) `shouldBe` " down"

    describe "chat api" $ do
      it "create chat completion" $ \cli -> do
        let completion =
              defaultChatCompletionRequest
                (ModelId "gpt-3.5-turbo")
                [ ChatMessage
                    { chmRole = "user",
                      chmContent = Just "What is the opposite of up? Answer in one word.",
                      chmFunctionCall = Nothing,
                      chmName = Nothing
                    }
                ]
        res <- forceSuccess $ completeChat cli completion
        chrChoices res `shouldNotBe` []
        chmContent (chchMessage (head (chrChoices res))) `shouldBe` Just "down."
      it "'content' is a required property" $ \cli -> do
        let completion =
              defaultChatCompletionRequest
                (ModelId "gpt-3.5-turbo")
                [ ChatMessage
                    { chmRole = "assistant",
                      chmContent = Nothing,
                      chmFunctionCall = Just $ ChatFunctionCall { chfcName = "f", chfcArguments = "{}" },
                      chmName = Nothing
                    },
                  ChatMessage
                    { chmRole = "function",
                      chmContent = Just "x",
                      chmFunctionCall = Nothing,
                      chmName = Just "f"
                    }
                ]
        res <- forceSuccess $ completeChat cli completion
        chrChoices res `shouldNotBe` []

    describe "edits api" $ do
      it "create edit" $ \cli -> do
        let edit =
              (defaultEditCreate (ModelId "text-davinci-edit-001") "Fox" "Pluralize the word")
                { edcrN = Just 1
                }
        res <- forceSuccess $ createTextEdit cli edit
        edrChoices res `shouldNotBe` []
        edchText (head $ edrChoices res) `shouldBe` "Foxes\n"

    -- TODO (2023.03.22): Create tests for images, audio APIs

    describe "embeddings api" $ do
      it "create embeddings" $ \cli -> do
        let embedding = EmbeddingCreate {embcModel = ModelId "text-embedding-ada-002", embcInput = "Hello", embcUser = Nothing}
        res <- forceSuccess $ createEmbedding cli embedding
        embrData res `shouldNotBe` []
        V.length (embdEmbedding (head $ embrData res)) `shouldBe` 1536

---------------------------------
------- 2022 API tests ----------
---------------------------------

apiTests2022 :: SpecWith ()
apiTests2022 =
  beforeAll makeClient $
    do
      describe "embeddings" $ do
        it "computes embeddings" $ \cli -> do
          res <- forceSuccess $ engineCreateEmbedding cli (EngineId "babbage-similarity") (EngineEmbeddingCreate "This is nice")
          V.null (olData res) `shouldBe` False
          let embedding = V.head (olData res)
          V.length (eneEmbedding embedding) `shouldBe` 2048
      describe "fine tuning" $ do
        it "allows creating fine-tuning" $ \cli -> do
          let file =
                FileCreate
                  { fcPurpose = "fine-tune",
                    fcDocuments =
                      [ FhFineTune $ FineTuneHunk "So sad. Label:" "sad",
                        FhFineTune $ FineTuneHunk "So happy. Label:" "happy"
                      ]
                  }
          createRes <- forceSuccess $ createFile cli file
          let ftc = defaultFineTuneCreate (fId createRes)
          res <- forceSuccess $ createFineTune cli ftc
          ftStatus res `shouldBe` "pending"
      describe "engines" $
        do
          it "lists engines" $ \cli ->
            do
              res <- forceSuccess $ listEngines cli
              V.null (olData res) `shouldBe` False
          it "retrieve engine" $ \cli ->
            do
              engineList <- forceSuccess $ listEngines cli
              let firstEngine = V.head (olData engineList)
              engine <- forceSuccess $ getEngine cli (eId firstEngine)
              engine `shouldBe` firstEngine
      describe "text completion" $
        do
          it "works (smoke test)" $ \cli ->
            do
              completionResults <-
                forceSuccess $
                  engineCompleteText cli (EngineId "text-curie-001") $
                    (defaultEngineTextCompletionCreate "Why is the house ")
                      { tccrMaxTokens = Just 2
                      }
              V.length (tcChoices completionResults) `shouldBe` 1
              T.length (tccText (V.head (tcChoices completionResults))) `shouldNotBe` 0