openai (empty) → 1.0.0
raw patch · 47 files changed
+4524/−0 lines, 47 filesdep +aesondep +basedep +bytestring
Dependencies added: aeson, base, bytestring, containers, filepath, http-api-data, http-client, http-client-tls, openai, servant, servant-client, servant-multipart-api, servant-multipart-client, tasty, tasty-hunit, text, time, vector
Files
- CHANGELOG.md +3/−0
- LICENSE +30/−0
- openai-example/Main.hs +33/−0
- openai.cabal +114/−0
- src/OpenAI/Prelude.hs +125/−0
- src/OpenAI/V1.hs +505/−0
- src/OpenAI/V1/Assistants.hs +136/−0
- src/OpenAI/V1/Audio.hs +14/−0
- src/OpenAI/V1/Audio/Speech.hs +69/−0
- src/OpenAI/V1/Audio/Transcriptions.hs +86/−0
- src/OpenAI/V1/Audio/Translations.hs +60/−0
- src/OpenAI/V1/AutoOr.hs +22/−0
- src/OpenAI/V1/Batches.hs +102/−0
- src/OpenAI/V1/Chat/Completions.hs +297/−0
- src/OpenAI/V1/ChunkingStrategy.hs +39/−0
- src/OpenAI/V1/DeletionStatus.hs +15/−0
- src/OpenAI/V1/Embeddings.hs +54/−0
- src/OpenAI/V1/Error.hs +20/−0
- src/OpenAI/V1/Files.hs +111/−0
- src/OpenAI/V1/FineTuning/Jobs.hs +196/−0
- src/OpenAI/V1/Images.hs +14/−0
- src/OpenAI/V1/Images/Edits.hs +76/−0
- src/OpenAI/V1/Images/Generations.hs +64/−0
- src/OpenAI/V1/Images/Image.hs +15/−0
- src/OpenAI/V1/Images/ResponseFormat.hs +18/−0
- src/OpenAI/V1/Images/Variations.hs +59/−0
- src/OpenAI/V1/ListOf.hs +16/−0
- src/OpenAI/V1/Message.hs +78/−0
- src/OpenAI/V1/Models.hs +36/−0
- src/OpenAI/V1/Moderations.hs +60/−0
- src/OpenAI/V1/Order.hs +14/−0
- src/OpenAI/V1/ResponseFormat.hs +43/−0
- src/OpenAI/V1/Threads.hs +86/−0
- src/OpenAI/V1/Threads/Messages.hs +145/−0
- src/OpenAI/V1/Threads/Runs.hs +294/−0
- src/OpenAI/V1/Threads/Runs/Steps.hs +184/−0
- src/OpenAI/V1/Tool.hs +77/−0
- src/OpenAI/V1/ToolCall.hs +35/−0
- src/OpenAI/V1/ToolResources.hs +44/−0
- src/OpenAI/V1/Uploads.hs +136/−0
- src/OpenAI/V1/Usage.hs +39/−0
- src/OpenAI/V1/VectorStores.hs +116/−0
- src/OpenAI/V1/VectorStores/FileBatches.hs +77/−0
- src/OpenAI/V1/VectorStores/FileCounts.hs +17/−0
- src/OpenAI/V1/VectorStores/Files.hs +77/−0
- src/OpenAI/V1/VectorStores/Status.hs +20/−0
- tasty/Main.hs +653/−0
+ CHANGELOG.md view
@@ -0,0 +1,3 @@+1.0.0:++- Initial release
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2024, Gabriella Gonzalez++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Gabriella Gonzalez nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ openai-example/Main.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-}++module Main where++import Data.Foldable (traverse_)+import OpenAI.V1+import OpenAI.V1.Chat.Completions++import qualified Data.Text as Text+import qualified Data.Text.IO as Text.IO+import qualified System.Environment as Environment++main :: IO ()+main = do+ key <- Environment.getEnv "OPENAI_KEY"++ clientEnv <- getClientEnv "https://api.openai.com"++ let Methods{ createChatCompletion } = makeMethods clientEnv (Text.pack key)++ text <- Text.IO.getLine++ ChatCompletionObject{ choices } <- createChatCompletion _CreateChatCompletion+ { messages = [ User{ content = [ Text{ text } ], name = Nothing } ]+ , model = "gpt-4o-mini"+ }++ let display Choice{ message } = Text.IO.putStrLn (messageToContent message)++ traverse_ display choices
+ openai.cabal view
@@ -0,0 +1,114 @@+cabal-version: 2.4+name: openai+version: 1.0.0+synopsis: Servant bindings to OpenAI+description: This package provides comprehensive and type-safe bindings+ to OpenAI using Servant+license: BSD-3-Clause+license-file: LICENSE+author: Gabriella Gonzalez+maintainer: GenuineGabriella@gmail.com+copyright: 2024 Gabriella Gonzalez+build-type: Simple+extra-source-files: CHANGELOG.md++library+ default-language: Haskell2010+ hs-source-dirs: src+ build-depends: base >=4.15.0.0 && < 5+ , aeson+ , bytestring+ , containers+ , filepath+ , http-api-data+ , http-client-tls+ , servant+ , servant-multipart-api+ , servant-client+ , servant-multipart-client+ , text+ , time+ , vector+ exposed-modules: OpenAI.V1+ OpenAI.V1.Assistants+ OpenAI.V1.Audio+ OpenAI.V1.Audio.Speech+ OpenAI.V1.Audio.Transcriptions+ OpenAI.V1.Audio.Translations+ OpenAI.V1.AutoOr+ OpenAI.V1.Batches+ OpenAI.V1.Chat.Completions+ OpenAI.V1.ChunkingStrategy+ OpenAI.V1.DeletionStatus+ OpenAI.V1.Embeddings+ OpenAI.V1.Error+ OpenAI.V1.Files+ OpenAI.V1.FineTuning.Jobs+ OpenAI.V1.Images+ OpenAI.V1.Images.Edits+ OpenAI.V1.Images.Generations+ OpenAI.V1.Images.Image+ OpenAI.V1.Images.ResponseFormat+ OpenAI.V1.Images.Variations+ OpenAI.V1.ListOf+ OpenAI.V1.Message+ OpenAI.V1.Models+ OpenAI.V1.Moderations+ OpenAI.V1.Order+ OpenAI.V1.ResponseFormat+ OpenAI.V1.Threads+ OpenAI.V1.Threads.Messages+ OpenAI.V1.Threads.Runs+ OpenAI.V1.Threads.Runs.Steps+ OpenAI.V1.Tool+ OpenAI.V1.ToolCall+ OpenAI.V1.ToolResources+ OpenAI.V1.Uploads+ OpenAI.V1.Usage+ OpenAI.V1.VectorStores+ OpenAI.V1.VectorStores.FileCounts+ OpenAI.V1.VectorStores.Files+ OpenAI.V1.VectorStores.FileBatches+ OpenAI.V1.VectorStores.Status+ other-modules: OpenAI.Prelude+ default-extensions: DataKinds+ , DeriveAnyClass+ , DeriveGeneric+ , DerivingStrategies+ , DuplicateRecordFields+ , FlexibleInstances+ , GeneralizedNewtypeDeriving+ , OverloadedLists+ , OverloadedStrings+ , RecordWildCards+ , MultiParamTypeClasses+ , NamedFieldPuns+ , TypeApplications+ , TypeOperators+ , ViewPatterns+ ghc-options: -Wall -Wno-missing-fields++test-suite tasty+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: tasty+ main-is: Main.hs+ build-depends: base+ , aeson+ , http-client+ , http-client-tls+ , openai+ , servant-client+ , tasty+ , tasty-hunit+ , text+ ghc-options: -Wall++executable openai-example+ default-language: Haskell2010+ hs-source-dirs: openai-example+ main-is: Main.hs+ build-depends: base+ , openai+ , text+ ghc-options: -Wall
+ src/OpenAI/Prelude.hs view
@@ -0,0 +1,125 @@+module OpenAI.Prelude+ ( -- * JSON+ aesonOptions+ , stripPrefix+ , labelModifier++ -- * Multipart Form Data+ , input+ , renderIntegral+ , renderRealFloat+ , getExtension++ -- * Re-exports+ , module Data.Aeson+ , module Data.ByteString.Lazy+ , module Data.List.NonEmpty+ , module Data.Map+ , module Data.String+ , module Data.Text+ , module Data.Time.Clock.POSIX+ , module Data.Vector+ , module Data.Void+ , module Data.Word+ , module GHC.Generics+ , module Numeric.Natural+ , module Servant.API+ , module Servant.Multipart.API+ , module Web.HttpApiData+ ) where++import Data.ByteString.Lazy (ByteString)+import Data.List.NonEmpty (NonEmpty(..))+import Data.Map (Map)+import Data.Text (Text)+import Data.Time.Clock.POSIX (POSIXTime)+import Data.Vector (Vector)+import Data.Void (Void)+import GHC.Generics (Generic)+import Data.String (IsString(..))+import Data.Word (Word8)+import Numeric.Natural (Natural)+import Web.HttpApiData (ToHttpApiData(..))++import Data.Aeson+ ( FromJSON(..)+ , ToJSON(..)+ , Options(..)+ , SumEncoding(..)+ , Value(..)+ , genericParseJSON+ , genericToJSON+ )+import Servant.API+ ( Accept(..)+ , Capture+ , Delete+ , Get+ , Header'+ , JSON+ , MimeUnrender(..)+ , OctetStream+ , Post+ , QueryParam+ , ReqBody+ , Required+ , Strict+ , (:<|>)(..)+ , (:>)+ )+import Servant.Multipart.API+ ( FileData(..)+ , Input(..)+ , MultipartData(..)+ , MultipartForm+ , ToMultipart(..)+ , Tmp+ )++import qualified Data.Aeson as Aeson+import qualified Data.Char as Char+import qualified Data.List as List+import qualified Data.Text as Text+import qualified Data.Text.Lazy as Text.Lazy+import qualified Data.Text.Lazy.Builder as Builder+import qualified Data.Text.Lazy.Builder.RealFloat as RealFloat+import qualified Data.Text.Lazy.Builder.Int as Int+import qualified System.FilePath as FilePath++dropTrailingUnderscore :: String -> String+dropTrailingUnderscore "_" = ""+dropTrailingUnderscore "" = ""+dropTrailingUnderscore (c : cs) = c : dropTrailingUnderscore cs++labelModifier :: String -> String+labelModifier = map Char.toLower . dropTrailingUnderscore++stripPrefix :: String -> String -> String+stripPrefix prefix string = labelModifier suffix+ where+ suffix = case List.stripPrefix prefix string of+ Nothing -> string+ Just x -> x++aesonOptions :: Options+aesonOptions = Aeson.defaultOptions+ { fieldLabelModifier = labelModifier+ , constructorTagModifier = labelModifier+ , omitNothingFields = True+ }++input :: Text -> Text -> [ Input ]+input iName iValue = [ Input{..} ]++renderIntegral :: Integral number => number -> Text+renderIntegral number = Text.Lazy.toStrict (Builder.toLazyText builder)+ where+ builder = Int.decimal number++renderRealFloat :: RealFloat number => number -> Text+renderRealFloat number = Text.Lazy.toStrict (Builder.toLazyText builder)+ where+ builder = RealFloat.formatRealFloat RealFloat.Fixed Nothing number++getExtension :: FilePath -> Text+getExtension file = Text.pack (drop 1 (FilePath.takeExtension file))
+ src/OpenAI/V1.hs view
@@ -0,0 +1,505 @@+-- | @\/v1@+module OpenAI.V1+ ( -- * Methods+ getClientEnv+ , makeMethods+ , Methods(..)++ -- * Servant+ , API+ ) where++import Data.ByteString.Char8 ()+import Data.Proxy (Proxy(..))+import OpenAI.Prelude+import OpenAI.V1.Audio.Speech (CreateSpeech)+import OpenAI.V1.Embeddings (CreateEmbeddings, EmbeddingObject)+import OpenAI.V1.Batches (BatchID, BatchObject, CreateBatch)+import OpenAI.V1.DeletionStatus (DeletionStatus)+import OpenAI.V1.Files (FileID, FileObject, UploadFile)+import OpenAI.V1.Images.Image (ImageObject)+import OpenAI.V1.Images.Generations (CreateImage)+import OpenAI.V1.Images.Edits (CreateImageEdit)+import OpenAI.V1.Images.Variations (CreateImageVariation)+import OpenAI.V1.ListOf (ListOf(..))+import OpenAI.V1.Message (Message)+import OpenAI.V1.Models (Model, ModelObject)+import OpenAI.V1.Moderations (CreateModeration, Moderation)+import OpenAI.V1.Order (Order)+import OpenAI.V1.Threads (Thread, ThreadID, ModifyThread, ThreadObject)+import OpenAI.V1.Threads.Runs.Steps (RunStepObject(..), StepID)+import Servant.Client (ClientEnv)+import Servant.Multipart.Client ()++import OpenAI.V1.Assistants+ (AssistantID, AssistantObject, CreateAssistant, ModifyAssistant)+import OpenAI.V1.Audio.Transcriptions+ (CreateTranscription, TranscriptionObject)+import OpenAI.V1.Audio.Translations+ (CreateTranslation, TranslationObject)+import OpenAI.V1.Chat.Completions+ (ChatCompletionObject, CreateChatCompletion)+import OpenAI.V1.FineTuning.Jobs+ ( CheckpointObject+ , CreateFineTuningJob+ , EventObject+ , FineTuningJobID+ , JobObject+ )+import OpenAI.V1.Threads.Messages+ (MessageID, MessageObject, ModifyMessage)+import OpenAI.V1.Threads.Runs+ ( CreateRun+ , CreateThreadAndRun+ , ModifyRun+ , RunID+ , RunObject+ , SubmitToolOutputsToRun+ )+import OpenAI.V1.Uploads+ ( AddUploadPart+ , CompleteUpload+ , CreateUpload+ , PartObject+ , UploadID+ , UploadObject+ )+import OpenAI.V1.VectorStores+ ( CreateVectorStore(..)+ , ModifyVectorStore(..)+ , VectorStoreID+ , VectorStoreObject(..)+ )+import OpenAI.V1.VectorStores.Files+ ( CreateVectorStoreFile(..)+ , VectorStoreFileID+ , VectorStoreFileObject(..)+ )+import OpenAI.V1.VectorStores.FileBatches+ ( CreateVectorStoreFileBatch(..)+ , VectorStoreFilesBatchObject(..)+ , VectorStoreFileBatchID+ )++import qualified Control.Exception as Exception+import qualified Data.Text as Text+import qualified Network.HTTP.Client.TLS as TLS+import qualified OpenAI.V1.Assistants as Assistants+import qualified OpenAI.V1.Audio as Audio+import qualified OpenAI.V1.Batches as Batches+import qualified OpenAI.V1.Chat.Completions as Chat.Completions+import qualified OpenAI.V1.Embeddings as Embeddings+import qualified OpenAI.V1.FineTuning.Jobs as FineTuning.Jobs+import qualified OpenAI.V1.Files as Files+import qualified OpenAI.V1.Images as Images+import qualified OpenAI.V1.Models as Models+import qualified OpenAI.V1.Moderations as Moderations+import qualified OpenAI.V1.Threads.Runs as Threads.Runs+import qualified OpenAI.V1.Threads.Runs.Steps as Threads.Runs.Steps+import qualified OpenAI.V1.Threads as Threads+import qualified OpenAI.V1.Threads.Messages as Messages+import qualified OpenAI.V1.Uploads as Uploads+import qualified OpenAI.V1.VectorStores as VectorStores+import qualified OpenAI.V1.VectorStores.Files as VectorStores.Files+import qualified OpenAI.V1.VectorStores.FileBatches as VectorStores.FileBatches+import qualified OpenAI.V1.VectorStores.Status as VectorStores.Status+import qualified Servant.Client as Client++-- | Convenient utility to get a `ClientEnv` for the most common use case+getClientEnv+ :: Text+ -- ^ Base URL for API+ -> IO ClientEnv+getClientEnv baseUrlText = do+ baseUrl <- Client.parseBaseUrl (Text.unpack baseUrlText)+ manager <- TLS.newTlsManager+ pure (Client.mkClientEnv manager baseUrl)++-- | Get a record of API methods after providing an API token+makeMethods+ :: ClientEnv+ -- ^+ -> Text+ -- ^ API token+ -> Methods+makeMethods clientEnv token = Methods{..}+ where+ authorization = "Bearer " <> token++ ( ( createSpeech+ :<|> createTranscription_+ :<|> createTranslation_+ )+ :<|> createChatCompletion+ :<|> createEmbeddings_+ :<|> ( createFineTuningJob+ :<|> listFineTuningJobs_+ :<|> listFineTuningEvents_+ :<|> listFineTuningCheckpoints_+ :<|> retrieveFineTuningJob+ :<|> cancelFineTuning+ )+ :<|> ( createBatch+ :<|> retrieveBatch+ :<|> cancelBatch+ :<|> listBatch_+ )+ :<|> ( uploadFile_+ :<|> listFiles_+ :<|> retrieveFile+ :<|> deleteFile+ :<|> retrieveFileContent+ )+ :<|> ( createImage_+ :<|> createImageEdit_+ :<|> createImageVariation_+ )+ :<|> ( createUpload+ :<|> addUploadPart_+ :<|> completeUpload+ :<|> cancelUpload+ )+ :<|> ( listModels_+ :<|> retrieveModel+ :<|> deleteModel+ )+ :<|> ( createModeration+ )+ :<|> ( (\x -> x "assistants=v2")+ -> ( createAssistant+ :<|> listAssistants_+ :<|> retrieveAssistant+ :<|> modifyAssistant+ :<|> deleteAssistant+ )+ )+ :<|> ( (\x -> x "assistants=v2")+ -> ( createThread+ :<|> retrieveThread+ :<|> modifyThread+ :<|> deleteThread+ )+ )+ :<|> ( (\x -> x "assistants=v2")+ -> ( createMessage+ :<|> listMessages_+ :<|> retrieveMessage+ :<|> modifyMessage+ :<|> deleteMessage+ )+ )+ :<|> ( (\x -> x "assistants=v2")+ -> ( createRun+ :<|> createThreadAndRun+ :<|> listRuns_+ :<|> retrieveRun+ :<|> modifyRun+ :<|> submitToolOutputsToRun+ :<|> cancelRun+ )+ )+ :<|> ( (\x -> x "assistants=v2")+ -> ( listRunSteps_+ :<|> retrieveRunStep+ )+ )+ :<|> ( (\x -> x "assistants=v2")+ -> ( createVectorStore+ :<|> listVectorStores_+ :<|> retrieveVectorStore+ :<|> modifyVectorStore+ :<|> deleteVectorStore+ )+ )+ :<|> ( (\x -> x "assistants=v2")+ -> ( createVectorStoreFile+ :<|> listVectorStoreFiles_+ :<|> retrieveVectorStoreFile+ :<|> deleteVectorStoreFile+ )+ )+ :<|> ( (\x -> x "assistants=v2")+ -> ( createVectorStoreFileBatch+ :<|> retrieveVectorStoreFileBatch+ :<|> cancelVectorStoreFileBatch+ :<|> listVectorStoreFilesInABatch_+ )+ )+ ) = Client.hoistClient @API Proxy run (Client.client @API Proxy) authorization++ run :: Client.ClientM a -> IO a+ run clientM = do+ result <- Client.runClientM clientM clientEnv+ case result of+ Left exception -> Exception.throwIO exception+ Right a -> return a++ toVector :: IO (ListOf a) -> IO (Vector a)+ toVector = fmap adapt+ where+ adapt List{ data_ } = data_++ createTranscription a = createTranscription_ (boundary, a)+ createTranslation a = createTranslation_ (boundary, a)+ createEmbeddings a = toVector (createEmbeddings_ a)+ listFineTuningJobs a b = toVector (listFineTuningJobs_ a b)+ listFineTuningEvents a b c = toVector (listFineTuningEvents_ a b c)+ listFineTuningCheckpoints a b c =+ toVector (listFineTuningCheckpoints_ a b c)+ listBatch a b = toVector (listBatch_ a b)+ uploadFile a = uploadFile_ (boundary, a)+ listFiles a b c d = toVector (listFiles_ a b c d)+ addUploadPart a b = addUploadPart_ a (boundary, b)+ createImage a = toVector (createImage_ a)+ createImageEdit a = toVector (createImageEdit_ (boundary, a))+ createImageVariation a = toVector (createImageVariation_ (boundary, a))+ listModels = toVector listModels_+ listAssistants a b c d = toVector (listAssistants_ a b c d)+ listMessages a = toVector (listMessages_ a)+ listRuns a b c d e = toVector (listRuns_ a b c d e)+ listRunSteps a b c d e f g = toVector (listRunSteps_ a b c d e f g)+ listVectorStores a b c d = toVector (listVectorStores_ a b c d)+ listVectorStoreFiles a b c d e f =+ toVector (listVectorStoreFiles_ a b c d e f)+ listVectorStoreFilesInABatch a b c d e f g =+ toVector (listVectorStoreFilesInABatch_ a b c d e f g)++-- | Hard-coded boundary to simplify the user-experience+--+-- I don't understand why `multipart-servant-client` insists on generating a+-- fresh boundary for each request (or why it doesn't handle that for you)+boundary :: ByteString+boundary = "j3qdD3XtDVjvva8IIqoBzHQAYwCenObtPMkxAFnylwFyU5xffWKoYrY"++-- | API methods+data Methods = Methods+ { createSpeech :: CreateSpeech -> IO ByteString+ , createTranscription :: CreateTranscription -> IO TranscriptionObject+ , createTranslation :: CreateTranslation -> IO TranslationObject+ , createChatCompletion :: CreateChatCompletion -> IO ChatCompletionObject+ , createEmbeddings :: CreateEmbeddings -> IO (Vector EmbeddingObject)+ , createFineTuningJob :: CreateFineTuningJob -> IO JobObject+ , listFineTuningJobs+ :: Maybe Text+ -- ^ after+ -> Maybe Natural+ -- ^ limit+ -> IO (Vector JobObject)+ , listFineTuningEvents+ :: FineTuningJobID+ -- ^+ -> Maybe Text+ -- ^ after+ -> Maybe Natural+ -- ^ limit+ -> IO (Vector EventObject)+ , listFineTuningCheckpoints+ :: FineTuningJobID+ -- ^+ -> Maybe Text+ -- ^ after+ -> Maybe Natural+ -- ^ limit+ -> IO (Vector CheckpointObject)+ , retrieveFineTuningJob :: FineTuningJobID -> IO JobObject+ , cancelFineTuning :: FineTuningJobID -> IO JobObject+ , createBatch :: CreateBatch -> IO BatchObject+ , retrieveBatch :: BatchID -> IO BatchObject+ , cancelBatch :: BatchID -> IO BatchObject+ , listBatch+ :: Maybe Text+ -- ^ after+ -> Maybe Natural+ -- ^ limit+ -> IO (Vector BatchObject)+ , uploadFile :: UploadFile -> IO FileObject+ , listFiles+ :: Maybe Files.Purpose+ -- ^ purpose+ -> Maybe Natural+ -- ^ limit+ -> Maybe Order+ -- ^ order+ -> Maybe Text+ -- ^ after+ -> IO (Vector FileObject)+ , retrieveFile :: FileID -> IO FileObject+ , deleteFile :: FileID -> IO DeletionStatus+ , retrieveFileContent :: FileID -> IO ByteString+ , createUpload+ :: CreateUpload -> IO (UploadObject (Maybe Void))+ , addUploadPart :: UploadID -> AddUploadPart -> IO PartObject+ , completeUpload+ :: UploadID -> CompleteUpload -> IO (UploadObject FileObject)+ , cancelUpload :: UploadID -> IO (UploadObject (Maybe Void))+ , createImage :: CreateImage -> IO (Vector ImageObject)+ , createImageEdit :: CreateImageEdit -> IO (Vector ImageObject)+ , createImageVariation :: CreateImageVariation -> IO (Vector ImageObject)+ , listModels :: IO (Vector ModelObject)+ , retrieveModel :: Model -> IO ModelObject+ , deleteModel :: Model -> IO DeletionStatus+ , createModeration :: CreateModeration -> IO Moderation+ , createAssistant :: CreateAssistant -> IO AssistantObject+ , listAssistants+ :: Maybe Natural+ -- ^ limit+ -> Maybe Order+ -- ^ order+ -> Maybe Text+ -- ^ after+ -> Maybe Text+ -- ^ before+ -> IO (Vector AssistantObject)+ , retrieveAssistant :: AssistantID -> IO AssistantObject+ , modifyAssistant :: AssistantID -> ModifyAssistant -> IO AssistantObject+ , deleteAssistant :: AssistantID -> IO DeletionStatus+ , createThread :: Thread -> IO ThreadObject+ , retrieveThread :: ThreadID -> IO ThreadObject+ , modifyThread :: ThreadID -> ModifyThread -> IO ThreadObject+ , deleteThread :: ThreadID -> IO DeletionStatus+ , createMessage :: ThreadID -> Message -> IO MessageObject+ , listMessages :: ThreadID -> IO (Vector MessageObject)+ , retrieveMessage :: ThreadID -> MessageID -> IO MessageObject+ , modifyMessage+ :: ThreadID -> MessageID -> ModifyMessage -> IO MessageObject+ , deleteMessage :: ThreadID -> MessageID -> IO DeletionStatus+ , createRun+ :: ThreadID+ -- ^+ -> Maybe Text+ -- ^ include[]+ -> CreateRun+ -- ^+ -> IO RunObject+ , createThreadAndRun :: CreateThreadAndRun -> IO RunObject+ , listRuns+ :: ThreadID+ -- ^+ -> Maybe Natural+ -- ^ limit+ -> Maybe Order+ -- ^ order+ -> Maybe Text+ -- ^ after+ -> Maybe Text+ -- ^ before+ -> IO (Vector RunObject)+ , retrieveRun :: ThreadID -> RunID -> IO RunObject+ , modifyRun :: ThreadID -> RunID -> ModifyRun -> IO RunObject+ , submitToolOutputsToRun+ :: ThreadID -> RunID -> SubmitToolOutputsToRun -> IO RunObject+ , cancelRun :: ThreadID -> RunID -> IO RunObject+ , listRunSteps+ :: ThreadID+ -- ^+ -> RunID+ -- ^+ -> Maybe Natural+ -- ^ limit+ -> Maybe Order+ -- ^ order+ -> Maybe Text+ -- ^ after+ -> Maybe Text+ -- ^ before+ -> Maybe Text+ -- ^ include[]+ -> IO (Vector RunStepObject)+ , retrieveRunStep+ :: ThreadID+ -- ^+ -> RunID+ -- ^+ -> StepID+ -- ^+ -> Maybe Text+ -- ^ include[]+ -> IO RunStepObject+ , createVectorStore :: CreateVectorStore -> IO VectorStoreObject+ , listVectorStores+ :: Maybe Natural+ -- ^ limit+ -> Maybe Order+ -- ^ order+ -> Maybe Text+ -- ^ after+ -> Maybe Text+ -- ^ before+ -> IO (Vector VectorStoreObject)+ , retrieveVectorStore :: VectorStoreID -> IO VectorStoreObject+ , modifyVectorStore+ :: VectorStoreID -> ModifyVectorStore -> IO VectorStoreObject+ , deleteVectorStore :: VectorStoreID -> IO DeletionStatus+ , createVectorStoreFile+ :: VectorStoreID -> CreateVectorStoreFile -> IO VectorStoreFileObject+ , listVectorStoreFiles+ :: VectorStoreID+ -- ^+ -> Maybe Natural+ -- ^ limit+ -> Maybe Order+ -- ^ order+ -> Maybe Text+ -- ^ after+ -> Maybe Text+ -- ^ before+ -> Maybe VectorStores.Status.Status+ -- ^ filter+ -> IO (Vector VectorStoreFileObject)+ , retrieveVectorStoreFile+ :: VectorStoreID -> VectorStoreFileID -> IO VectorStoreFileObject+ , deleteVectorStoreFile+ :: VectorStoreID -> VectorStoreFileID -> IO DeletionStatus+ , createVectorStoreFileBatch+ :: VectorStoreID+ -> CreateVectorStoreFileBatch+ -> IO VectorStoreFilesBatchObject+ , retrieveVectorStoreFileBatch+ :: VectorStoreID+ -> VectorStoreFileBatchID+ -> IO VectorStoreFilesBatchObject+ , cancelVectorStoreFileBatch+ :: VectorStoreID+ -> VectorStoreFileBatchID+ -> IO VectorStoreFilesBatchObject+ , listVectorStoreFilesInABatch+ :: VectorStoreID+ -- ^+ -> VectorStoreFileBatchID+ -- ^+ -> Maybe Natural+ -- ^ limit+ -> Maybe Order+ -- ^ order+ -> Maybe Text+ -- ^ after+ -> Maybe Text+ -- ^ before+ -> Maybe VectorStores.Status.Status+ -- ^ filter+ -> IO (Vector VectorStoreFilesBatchObject)+ }++-- | Servant API+type API+ = Header' [ Required, Strict ] "Authorization" Text+ :> "v1"+ :> ( Audio.API+ :<|> Chat.Completions.API+ :<|> Embeddings.API+ :<|> FineTuning.Jobs.API+ :<|> Batches.API+ :<|> Files.API+ :<|> Images.API+ :<|> Uploads.API+ :<|> Models.API+ :<|> Moderations.API+ :<|> Assistants.API+ :<|> Threads.API+ :<|> Messages.API+ :<|> Threads.Runs.API+ :<|> Threads.Runs.Steps.API+ :<|> VectorStores.API+ :<|> VectorStores.Files.API+ :<|> VectorStores.FileBatches.API+ )
+ src/OpenAI/V1/Assistants.hs view
@@ -0,0 +1,136 @@+-- | @\/v1\/assistants@+module OpenAI.V1.Assistants+ ( -- * Main types+ AssistantID(..)+ , CreateAssistant(..)+ , _CreateAssistant+ , ModifyAssistant(..)+ , _ModifyAssistant+ , AssistantObject(..)++ -- * Other types+ , RankingOptions+ , FileSearch(..)+ , Function(..)+ , Tool(..)+ , CodeInterpreterResources(..)+ , FileSearchResources(..)+ , ToolResources(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.AutoOr+import OpenAI.V1.DeletionStatus+import OpenAI.V1.ListOf+import OpenAI.V1.Models (Model)+import OpenAI.V1.Order+import OpenAI.V1.ResponseFormat+import OpenAI.V1.Tool+import OpenAI.V1.ToolResources++-- | AssistantID+newtype AssistantID = AssistantID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | Request body for @\/v1\/assistants@+data CreateAssistant = CreateAssistant+ { model :: Model+ , name :: Maybe Text+ , description :: Maybe Text+ , instructions :: Maybe Text+ , tools :: Maybe (Vector Tool)+ , tool_resources :: Maybe ToolResources+ , metadata :: Maybe (Map Text Text)+ , temperature :: Maybe Double+ , top_p :: Maybe Double+ , response_format :: Maybe (AutoOr ResponseFormat)+ } deriving stock (Generic, Show)++instance ToJSON CreateAssistant where+ toJSON = genericToJSON aesonOptions++-- | Default `CreateAssistant`+_CreateAssistant :: CreateAssistant+_CreateAssistant = CreateAssistant+ { name = Nothing+ , description = Nothing+ , instructions = Nothing+ , tools = Nothing+ , tool_resources = Nothing+ , metadata = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , response_format = Nothing+ }++-- | Request body for @\/v1\/assistants/:assistant_id@+data ModifyAssistant = ModifyAssistant+ { model :: Model+ , name :: Maybe Text+ , description :: Maybe Text+ , instructions :: Maybe Text+ , tools :: Maybe (Vector Tool)+ , tool_resources :: Maybe ToolResources+ , metadata :: Maybe (Map Text Text)+ , temperature :: Maybe Double+ , top_p :: Maybe Double+ , response_format :: Maybe (AutoOr ResponseFormat)+ } deriving stock (Generic, Show)++instance ToJSON ModifyAssistant where+ toJSON = genericToJSON aesonOptions++-- | Default `ModifyAssistant`+_ModifyAssistant :: ModifyAssistant+_ModifyAssistant = ModifyAssistant+ { name = Nothing+ , description = Nothing+ , instructions = Nothing+ , tools = Nothing+ , tool_resources = Nothing+ , metadata = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , response_format = Nothing+ }++-- | Represents an assistant that can call the model and use tools.+data AssistantObject = AssistantObject+ { id :: AssistantID+ , object :: Text+ , created_at :: POSIXTime+ , name :: Maybe Text+ , description :: Maybe Text+ , model :: Model+ , instructions :: Maybe Text+ , tools :: Maybe (Vector Tool)+ , tool_resources :: Maybe ToolResources+ , metadata :: Map Text Text+ , temperature :: Maybe Double+ , top_p :: Maybe Double+ , response_format :: AutoOr ResponseFormat+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ Header' '[Required, Strict] "OpenAI-Beta" Text+ :> "assistants"+ :> ( ReqBody '[JSON] CreateAssistant+ :> Post '[JSON] AssistantObject+ :<|> QueryParam "limit" Natural+ :> QueryParam "order" Order+ :> QueryParam "after" Text+ :> QueryParam "before" Text+ :> Get '[JSON] (ListOf AssistantObject)+ :<|> Capture "assistant_id" AssistantID+ :> Get '[JSON] AssistantObject+ :<|> Capture "assistant_id" AssistantID+ :> ReqBody '[JSON] ModifyAssistant+ :> Post '[JSON] AssistantObject+ :<|> Capture "assistant_id" AssistantID+ :> Delete '[JSON] DeletionStatus+ )
+ src/OpenAI/V1/Audio.hs view
@@ -0,0 +1,14 @@+-- | @\/v1\/audio@+module OpenAI.V1.Audio+ ( -- * Servant+ API+ ) where++import OpenAI.Prelude+import qualified OpenAI.V1.Audio.Speech as Speech+import qualified OpenAI.V1.Audio.Transcriptions as Transcriptions+import qualified OpenAI.V1.Audio.Translations as Translations++-- | Servant API+type API =+ "audio" :> (Speech.API :<|> Transcriptions.API :<|> Translations.API)
+ src/OpenAI/V1/Audio/Speech.hs view
@@ -0,0 +1,69 @@+-- | @\/v1\/audio\/speech@+module OpenAI.V1.Audio.Speech+ ( -- * Main types+ CreateSpeech(..)+ , _CreateSpeech+ -- * Other types+ , Voice(..)+ , Format(..)+ -- * Servant+ , ContentType(..)+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Models (Model)++-- | The voice to use when generating the audio+--+-- Previews of the voices are available in the+-- [Text to speech guide](https://platform.openai.com/docs/guides/text-to-speech#voice-options).+data Voice = Alloy | Echo | Fable | Onyx | Nova | Shimmer+ deriving stock (Bounded, Enum, Generic, Show)++instance ToJSON Voice where+ toJSON = genericToJSON aesonOptions++-- | The format to audio in+data Format = MP3 | Opus | AAC | FLAC | WAV | PCM+ deriving stock (Bounded, Enum, Generic, Show)++instance ToJSON Format where+ toJSON = genericToJSON aesonOptions++-- | Request body for @\/v1\/audio\/speech@+data CreateSpeech = CreateSpeech+ { model :: Model+ , input :: Text+ , voice :: Voice+ , response_format :: Maybe Format+ , speed :: Maybe Double+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Default `CreateSpeech`+_CreateSpeech :: CreateSpeech+_CreateSpeech = CreateSpeech+ { response_format = Nothing+ , speed = Nothing+ }++-- | Content type+data ContentType = ContentType++instance Accept ContentType where+ contentTypes _ =+ "audio/mpeg"+ :| [ "audio/flac"+ , "audio/wav"+ , "audio/aac"+ , "audio/opus"+ , "audio/pcm"+ ]++instance MimeUnrender ContentType ByteString where+ mimeUnrender _ bytes = Right bytes++-- | Servant API+type API =+ "speech" :> ReqBody '[JSON] CreateSpeech :> Post '[ContentType] ByteString
+ src/OpenAI/V1/Audio/Transcriptions.hs view
@@ -0,0 +1,86 @@+-- | @\/v1\/audio\/transcriptions@+--+-- To simplify things, this only supports the @verbose_json@ response format+-- and also only supports the @segment@ granularity+module OpenAI.V1.Audio.Transcriptions+ ( -- * Main types+ CreateTranscription(..)+ , _CreateTranscription+ , TranscriptionObject(..)+ -- * Other types+ , Segment(..)+ -- * Servant+ , API+ ) where++import OpenAI.Prelude as OpenAI.Prelude+import OpenAI.V1.Models (Model(..))++import qualified Data.Text as Text++-- | Request body for @\/v1\/audio\/transcriptions@+data CreateTranscription = CreateTranscription+ { file :: FilePath+ , model :: Model+ , language :: Maybe Text+ , prompt :: Maybe Text+ , temperature :: Maybe Double+ } deriving stock (Generic, Show)++instance ToMultipart Tmp CreateTranscription where+ toMultipart CreateTranscription{ model = Model model, ..} =+ MultipartData{..}+ where+ inputs =+ input "model" model+ <> foldMap (input "language") language+ <> foldMap (input "prompt") prompt+ <> input "response_format" "verbose_json"+ <> foldMap (input "temperature" . renderRealFloat) temperature+ <> input "timestamp_granularities[]" "segment"++ files = [ FileData{..} ]+ where+ fdInputName = "file"+ fdFileName = Text.pack file+ fdFileCType = "audio/" <> getExtension file+ fdPayload = file++-- | Default `CreateTranscription`+_CreateTranscription :: CreateTranscription+_CreateTranscription = CreateTranscription+ { language = Nothing+ , prompt = Nothing+ , temperature = Nothing+ }++-- | Segment of the transcribed text and its corresponding details+data Segment = Segment+ { id :: Integer+ , seek :: Integer+ , start :: Double+ , end :: Double+ , text :: Text+ , tokens :: Vector Prelude.Word+ , temperature :: Double+ , avg_logprob :: Double+ , compression_ratio :: Double+ , no_speech_prob :: Double+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Represents a verbose json transcription response returned by model, based+-- on the provided input.+data TranscriptionObject = TranscriptionObject+ { language :: Maybe Text+ , duration :: Maybe Double+ , text :: Text+ , segments :: Vector Segment+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "transcriptions"+ :> MultipartForm Tmp CreateTranscription+ :> Post '[JSON] TranscriptionObject
+ src/OpenAI/V1/Audio/Translations.hs view
@@ -0,0 +1,60 @@+-- | @\/v1\/audio\/translations@+--+-- To simplify things, this only supports the @verbose_json@ response format+module OpenAI.V1.Audio.Translations+ ( -- * Main types+ CreateTranslation(..)+ , _CreateTranslation+ , TranslationObject(..)+ -- * Servant+ , API+ ) where++import OpenAI.Prelude as OpenAI.Prelude+import OpenAI.V1.Models (Model(..))++import qualified Data.Text as Text++-- | Request body for @\/v1\/audio\/translations@+data CreateTranslation = CreateTranslation+ { file :: FilePath+ , model :: Model+ , prompt :: Maybe Text+ , temperature :: Maybe Double+ } deriving stock (Generic, Show)++instance ToMultipart Tmp CreateTranslation where+ toMultipart CreateTranslation{ model = Model model, ..} = MultipartData{..}+ where+ inputs =+ input "model" model+ <> foldMap (input "prompt") prompt+ <> input "response_format" "verbose_json"+ <> foldMap (input "temperature" . renderRealFloat) temperature++ files = [ FileData{..} ]+ where+ fdInputName = "file"+ fdFileName = Text.pack file+ fdFileCType = "audio/" <> getExtension file+ fdPayload = file++-- | Default `CreateTranslation`+_CreateTranslation :: CreateTranslation+_CreateTranslation = CreateTranslation+ { prompt = Nothing+ , temperature = Nothing+ }++-- | Represents a transcription response returned by model, based on the+-- provided input.+data TranslationObject = TranslationObject+ { text :: Text+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "translations"+ :> MultipartForm Tmp CreateTranslation+ :> Post '[JSON] TranslationObject
+ src/OpenAI/V1/AutoOr.hs view
@@ -0,0 +1,22 @@+-- | The `AutoOr` type constructor+module OpenAI.V1.AutoOr+ ( -- * Types+ AutoOr(..)+ ) where++import OpenAI.Prelude++-- | A type that can also be the string @\"auto\"@+data AutoOr a = Auto | Specific a+ deriving stock (Generic, Show)++instance FromJSON a => FromJSON (AutoOr a) where+ parseJSON "auto" = pure Auto+ parseJSON value = fmap Specific (parseJSON value)++instance ToJSON a => ToJSON (AutoOr a) where+ toJSON Auto = "auto"+ toJSON (Specific a) = toJSON a++instance IsString a => IsString (AutoOr a) where+ fromString string = Specific (fromString string)
+ src/OpenAI/V1/Batches.hs view
@@ -0,0 +1,102 @@+-- | @\/v1\/batches@+module OpenAI.V1.Batches+ ( -- * Main types+ BatchID(..)+ , CreateBatch(..)+ , _CreateBatch+ , BatchObject(..)+ -- * Other types+ , Status(..)+ , Counts(..)+ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Error+import OpenAI.V1.Files (FileID)+import OpenAI.V1.ListOf++-- | Batch ID+newtype BatchID = BatchID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | Request body for @\/v1\/batches@+data CreateBatch = CreateBatch+ { input_file_id :: FileID+ , endpoint :: Text+ , completion_window :: Text+ , metadata :: Maybe (Map Text Text)+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Default `CreateBatch`+_CreateBatch :: CreateBatch+_CreateBatch = CreateBatch+ { metadata = Nothing+ }++-- | The current status of the batch.+data Status+ = Validating+ | Failed+ | In_Progress+ | Finalizing+ | Completed+ | Expired+ | Cancelling+ | Cancelled+ deriving stock (Generic, Show)++instance FromJSON Status where+ parseJSON = genericParseJSON aesonOptions++-- | The request counts for different statuses within the batch.+data Counts = Counts+ { total :: Natural+ , completed :: Natural+ , failed :: Natural+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | The batch object+data BatchObject = BatchObject+ { id :: BatchID+ , object :: Text+ , endpoint :: Text+ , errors :: Maybe (ListOf Error)+ , input_file_id :: FileID+ , completion_window :: Text+ , status :: Status+ , output_file_id :: Maybe FileID+ , error_file_id :: Maybe FileID+ , created_at :: POSIXTime+ , in_progress_at :: Maybe POSIXTime+ , expires_at :: Maybe POSIXTime+ , finalizing_at :: Maybe POSIXTime+ , completed_at :: Maybe POSIXTime+ , failed_at :: Maybe POSIXTime+ , expired_at :: Maybe POSIXTime+ , cancelling_at :: Maybe POSIXTime+ , cancelled_at :: Maybe POSIXTime+ , request_counts :: Maybe Counts+ , metadata :: Maybe (Map Text Text)+ } deriving stock (Generic, Show)++instance FromJSON BatchObject where+ parseJSON = genericParseJSON aesonOptions++-- | Servant API+type API =+ "batches"+ :> ( ReqBody '[JSON] CreateBatch+ :> Post '[JSON] BatchObject+ :<|> Capture "batch_id" BatchID+ :> Get '[JSON] BatchObject+ :<|> Capture "batch_id" BatchID+ :> "cancel"+ :> Post '[JSON] BatchObject+ :<|> QueryParam "after" Text+ :> QueryParam "limit" Natural+ :> Get '[JSON] (ListOf BatchObject)+ )
+ src/OpenAI/V1/Chat/Completions.hs view
@@ -0,0 +1,297 @@+-- | @\/v1\/chat\/completions@+--+-- Streaming results are not yet supported+module OpenAI.V1.Chat.Completions+ ( -- * Main types+ CreateChatCompletion(..)+ , _CreateChatCompletion+ , ChatCompletionObject(..)+ , Choice(..)+ , Message(..)+ , messageToContent+ , Content(..)+ -- * Other types+ , InputAudio(..)+ , ImageURL(..)+ , AudioData(..)+ , Modality(..)+ , Prediction(..)+ , Voice(..)+ , AudioFormat(..)+ , AudioParameters(..)+ , ResponseFormat(..)+ , ServiceTier(..)+ , FinishReason(..)+ , Token(..)+ , LogProbs(..)+ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.AutoOr+import OpenAI.V1.Models (Model)+import OpenAI.V1.ResponseFormat+import OpenAI.V1.Tool+import OpenAI.V1.ToolCall+import OpenAI.V1.Usage+import Prelude hiding (id)++-- | Audio content part+data InputAudio = InputAudio{ data_ :: Text, format :: AudioFormat }+ deriving stock (Generic, Show)++instance ToJSON InputAudio where+ toJSON = genericToJSON aesonOptions++-- | Image content part+data ImageURL = ImageURL{ url :: Text, detail :: Maybe (AutoOr Text) }+ deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | A content part+data Content+ = Text{ text :: Text }+ | Image_URL{ image_url :: ImageURL }+ | Input_Audio{ input_audio :: InputAudio }+ deriving (Generic, Show)++instance IsString Content where+ fromString string = Text{ text = fromString string }++instance ToJSON Content where+ toJSON = genericToJSON aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True+ }++-- | Data about a previous audio response from the model.+-- [Learn more](https://platform.openai.com/docs/guides/audio)+data AudioData = AudioData{ id :: Text }+ deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | A message from the conversation so far+data Message content+ = System+ { content :: content+ , name :: Maybe Text+ }+ | User+ { content :: content+ , name :: Maybe Text+ }+ | Assistant+ { assistant_content :: Maybe content+ , refusal :: Maybe Text+ , name :: Maybe Text+ , assistant_audio :: Maybe AudioData+ , tool_calls :: Maybe (Vector ToolCall)+ }+ | Tool+ { content :: content+ , tool_call_id :: Text+ }+ deriving stock (Generic, Show)++messageOptions :: Options+messageOptions = aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "role", contentsFieldName = "" }++ , tagSingleConstructors = True++ , fieldLabelModifier = stripPrefix "assistant_"+ }++instance FromJSON content => FromJSON (Message content) where+ parseJSON = genericParseJSON messageOptions++instance ToJSON content => ToJSON (Message content) where+ toJSON = genericToJSON messageOptions++-- | Extract the message body from a `Message`+--+-- Normally this would just be the @content@ field selector, but the problem+-- is that the content field for the `Assistant` constructor is not required+-- to be present, so we provide a utility function to default to extract the+-- @content@ field for all constructors, defaulting to `mempty` for the special+-- case where the `Message` is an `Assistant` constructor with a missing+-- @content@ field+messageToContent :: Monoid content => Message content -> content+messageToContent System{ content } = content+messageToContent User{ content } = content+messageToContent Assistant{ assistant_content = Just content } = content+messageToContent Assistant{ assistant_content = Nothing } = mempty+messageToContent Tool{ content } = content++-- | Output types that you would like the model to generate for this request+data Modality = Modality_Text | Modality_Audio+ deriving stock (Generic, Show)++instance ToJSON Modality where+ toJSON = genericToJSON aesonOptions+ { constructorTagModifier = stripPrefix "Modality_" }++-- | Configuration for a+-- [Predicted Output](https://platform.openai.com/docs/guides/predicted-outputs),+-- which can greatly improve response times when large parts of the model+-- response are known ahead of time. This is most common when you are+-- regenerating a file with only minor changes to most of the content+data Prediction = Content{ content :: Text }+ deriving stock (Generic, Show)++instance ToJSON Prediction where+ toJSON = genericToJSON aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True+ }++-- | The voice the model uses to respond+data Voice = Ash | Ballad | Coral | Sage | Verse+ deriving stock (Generic, Show)++instance ToJSON Voice where+ toJSON = genericToJSON aesonOptions++-- | Specifies the output audio format+data AudioFormat = WAV | MP3 | FLAC | Opus | PCM16+ deriving stock (Generic, Show)++instance ToJSON AudioFormat where+ toJSON = genericToJSON aesonOptions++-- | Parameters for audio output+data AudioParameters = AudioParameters+ { voice :: Voice+ , format :: AudioFormat+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Specifies the latency tier to use for processing the request+data ServiceTier = Default+ deriving stock (Generic, Show)++instance FromJSON ServiceTier where+ parseJSON = genericParseJSON aesonOptions{ tagSingleConstructors = True }++instance ToJSON ServiceTier where+ toJSON = genericToJSON aesonOptions++-- | Request body for @\/v1\/chat\/completions@+data CreateChatCompletion = CreateChatCompletion+ { messages :: Vector (Message (Vector Content))+ , model :: Model+ , store :: Maybe Bool+ , metadata :: Maybe (Map Text Text)+ , frequency_penalty :: Maybe Double+ , logit_bias :: Maybe (Map Word Int)+ , logprobs :: Maybe Bool+ , top_logprobs :: Maybe Word+ , max_completion_tokens :: Maybe Natural+ , n :: Maybe Natural+ , modalities :: Maybe (Vector Modality)+ , prediction :: Maybe Prediction+ , audio :: Maybe AudioParameters+ , presence_penalty :: Maybe Double+ , response_format :: Maybe ResponseFormat+ , seed :: Maybe Integer+ , service_tier :: Maybe (AutoOr ServiceTier)+ , stop :: Maybe (Vector Text)+ , temperature :: Maybe Double+ , top_p :: Maybe Double+ , tools :: Maybe (Vector Tool)+ , tool_choice :: Maybe ToolChoice+ , parallel_tool_calls :: Maybe Bool+ , user :: Maybe Text+ } deriving stock (Generic, Show)++instance ToJSON CreateChatCompletion where+ toJSON = genericToJSON aesonOptions++-- | Default `CreateChatCompletion`+_CreateChatCompletion :: CreateChatCompletion+_CreateChatCompletion = CreateChatCompletion+ { store = Nothing+ , metadata = Nothing+ , frequency_penalty = Nothing+ , logit_bias = Nothing+ , logprobs = Nothing+ , top_logprobs = Nothing+ , max_completion_tokens = Nothing+ , n = Nothing+ , modalities = Nothing+ , prediction = Nothing+ , audio = Nothing+ , presence_penalty = Nothing+ , response_format = Nothing+ , seed = Nothing+ , service_tier = Nothing+ , stop = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , tools = Nothing+ , tool_choice = Nothing+ , parallel_tool_calls = Nothing+ , user = Nothing+ }++-- | The reason the model stopped generating tokens+data FinishReason+ = Stop+ | Length+ | Content_Filter+ | Tool_Calls+ deriving stock (Generic, Show)++instance FromJSON FinishReason where+ parseJSON = genericParseJSON messageOptions++-- | Message tokens with log probability information+data Token = Token+ { token :: Text+ , logprob :: Double+ , bytes :: Maybe (Vector Word8)+ , top_logprobs :: Maybe (Vector Token)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Log probability information for the choice+data LogProbs = LogProbs+ { content :: Maybe (Vector Token)+ , refusal :: Maybe (Vector Token)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | A chat completion choice+data Choice = Choice+ { finish_reason :: Text+ , index :: Natural+ , message :: Message Text+ , logprobs :: Maybe LogProbs+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | ChatCompletion body+data ChatCompletionObject = ChatCompletionObject+ { id :: Text+ , choices :: Vector Choice+ , created :: POSIXTime+ , model :: Model+ , service_tier :: Maybe ServiceTier+ , system_fingerprint :: Text+ , object :: Text+ , usage :: Usage CompletionTokensDetails PromptTokensDetails+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "chat"+ :> "completions"+ :> ReqBody '[JSON] CreateChatCompletion+ :> Post '[JSON] ChatCompletionObject
+ src/OpenAI/V1/ChunkingStrategy.hs view
@@ -0,0 +1,39 @@+-- | The `ChunkingStrategy` type+module OpenAI.V1.ChunkingStrategy+ ( -- * Main types+ ChunkingStrategy(..)++ -- * Other types+ , Static(..)+ ) where++import OpenAI.Prelude++-- | Static chunking strategy+data Static = Static+ { max_chunk_size_tokens :: Natural+ , chunk_overlap_tokens :: Natural+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | The chunking strategy used to chunk the file(s)+data ChunkingStrategy = ChunkingStrategy_Static{ static :: Static }+ deriving stock (Generic, Show)++chunkingStrategyOptions :: Options+chunkingStrategyOptions = aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True++ , constructorTagModifier = stripPrefix "ChunkingStrategy_"+ }++instance ToJSON ChunkingStrategy where+ toJSON = genericToJSON chunkingStrategyOptions++instance FromJSON ChunkingStrategy where+ parseJSON = genericParseJSON chunkingStrategyOptions++
+ src/OpenAI/V1/DeletionStatus.hs view
@@ -0,0 +1,15 @@+-- | The `DeletionStatus` type+module OpenAI.V1.DeletionStatus+ ( -- * Types+ DeletionStatus(..)+ ) where++import OpenAI.Prelude++-- | Deletion status+data DeletionStatus = DeletionStatus+ { id :: Text+ , object :: Text+ , deleted :: Bool+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)
+ src/OpenAI/V1/Embeddings.hs view
@@ -0,0 +1,54 @@+-- | @\/v1\/embeddings@+module OpenAI.V1.Embeddings+ ( -- * Main types+ CreateEmbeddings(..)+ , _CreateEmbeddings+ , EmbeddingObject(..)+ -- * Other types+ , EncodingFormat(..)+ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.ListOf+import OpenAI.V1.Models (Model)++-- | The format to return the embeddings in+data EncodingFormat = Float | Base64+ deriving stock (Generic, Show)++instance ToJSON EncodingFormat where+ toJSON = genericToJSON aesonOptions++-- | Request body for @\/v1\/embeddings@+data CreateEmbeddings = CreateEmbeddings+ { input :: Text+ , model :: Model+ , encoding_format :: Maybe EncodingFormat+ , dimensions :: Maybe Natural+ , user :: Maybe Text+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Default `CreateEmbeddings`+_CreateEmbeddings :: CreateEmbeddings+_CreateEmbeddings = CreateEmbeddings+ { encoding_format = Nothing+ , dimensions = Nothing+ , user = Nothing+ }++-- | The embedding object+data EmbeddingObject = EmbbeddingObject+ { index :: Natural+ , embedding :: Vector Double+ , object :: Text+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "embeddings"+ :> ReqBody '[JSON] CreateEmbeddings+ :> Post '[JSON] (ListOf EmbeddingObject)
+ src/OpenAI/V1/Error.hs view
@@ -0,0 +1,20 @@+-- | Error information+module OpenAI.V1.Error+ ( -- * Types+ Error(..)+ ) where++import OpenAI.Prelude++-- | More information on the cause of the failure.+--+-- NOTE: OpenAPI API's says that the `code` and `message` fields are required,+-- but in practice the `Error` record can be present with all fields omitted,+-- so they are all marked optional (`Maybe`) here+data Error = Error+ { code :: Maybe Text+ , message :: Maybe Text+ , param :: Maybe Text+ , line :: Maybe Natural+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)
+ src/OpenAI/V1/Files.hs view
@@ -0,0 +1,111 @@+-- | @\/v1\/files@+module OpenAI.V1.Files+ ( -- * Main types+ FileID(..)+ , UploadFile(..)+ , _UploadFile+ , FileObject(..)+ -- * Other types+ , Order(..)+ , Purpose(..)+ , DeletionStatus(..)+ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.DeletionStatus+import OpenAI.V1.ListOf+import OpenAI.V1.Order++import qualified Data.Text as Text++-- | File ID+newtype FileID = FileID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | UploadFile body+data UploadFile = UploadFile+ { file :: FilePath+ , purpose :: Purpose+ } deriving stock (Generic, Show)++-- | Default `UploadFile`+_UploadFile :: UploadFile+_UploadFile = UploadFile{ }++instance ToMultipart Tmp UploadFile where+ toMultipart UploadFile{..} = MultipartData{..}+ where+ inputs = input "purpose" (toUrlPiece purpose)++ files = [ FileData{..} ]+ where+ fdInputName = "file"+ fdFileName = Text.pack file+ fdFileCType = "application/json"+ fdPayload = file++-- | The File object represents a document that has been uploaded to OpenAI+data FileObject = FileObject+ { id :: FileID+ , bytes :: Natural+ , created_at :: POSIXTime+ , filename :: Text+ , object :: Text+ , purpose :: Purpose+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | The intended purpose of the uploaded file.+data Purpose+ = Assistants+ | Assistants_Output+ | Batch+ | Batch_Output+ | Fine_Tune+ | Fine_Tune_Results+ | Vision+ deriving stock (Generic, Show)++purposeOptions :: Options+purposeOptions = aesonOptions+ { constructorTagModifier = fix . labelModifier }+ where+ fix "fine_tune" = "fine-tune"+ fix "fine_tune_results" = "fine-tune-results"+ fix string = string++instance FromJSON Purpose where+ parseJSON = genericParseJSON purposeOptions++instance ToJSON Purpose where+ toJSON = genericToJSON purposeOptions++instance ToHttpApiData Purpose where+ toUrlPiece Assistants = "assistants"+ toUrlPiece Assistants_Output = "assistants"+ toUrlPiece Batch = "batch"+ toUrlPiece Batch_Output = "batch_output"+ toUrlPiece Fine_Tune = "fine-tune"+ toUrlPiece Fine_Tune_Results = "fine-tune-results"+ toUrlPiece Vision = "vision"++-- | Servant API+type API =+ "files"+ :> ( MultipartForm Tmp UploadFile+ :> Post '[JSON] FileObject+ :<|> QueryParam "purpose" Purpose+ :> QueryParam "limit" Natural+ :> QueryParam "order" Order+ :> QueryParam "after" Text+ :> Get '[JSON] (ListOf FileObject)+ :<|> Capture "file_id" FileID+ :> Get '[JSON] FileObject+ :<|> Capture "file_id" FileID+ :> Delete '[JSON] DeletionStatus+ :<|> Capture "file_id" FileID+ :> "content"+ :> Get '[OctetStream] ByteString+ )
+ src/OpenAI/V1/FineTuning/Jobs.hs view
@@ -0,0 +1,196 @@+-- | @\/v1\/fine_tuning/jobs@+module OpenAI.V1.FineTuning.Jobs+ ( -- * Main types+ FineTuningJobID(..)+ , CreateFineTuningJob(..)+ , _CreateFineTuningJob+ , JobObject(..)+ , EventObject(..)+ , CheckpointObject(..)++ -- * Other types+ , AutoOr(..)+ , Hyperparameters(..)+ , WAndB(..)+ , Integration(..)+ , Status(..)+ , Level(..)+ , Metrics(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.AutoOr+import OpenAI.V1.Error+import OpenAI.V1.Files (FileID)+import OpenAI.V1.Models (Model)+import OpenAI.V1.ListOf++-- | Fine tuning job ID+newtype FineTuningJobID = FineTuningJobID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | The hyperparameters used for the fine-tuning job+data Hyperparameters = Hyperparameters+ { batch_size :: Maybe (AutoOr Natural)+ , learning_rate_multiplier :: Maybe (AutoOr Double)+ , n_epochs :: Maybe (AutoOr Natural)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | The settings for your integration with Weights and+data WAndB = WAndB+ { project :: Text+ , name :: Maybe Text+ , entity :: Maybe Text+ , tags :: Maybe (Vector Text)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | An integration to enable for your fine-tuning job+data Integration = Integration_WAndB{ wandb :: WAndB }+ deriving stock (Generic, Show)++integrationOptions :: Options+integrationOptions = aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True++ , constructorTagModifier = stripPrefix "Integration_"+ }++instance FromJSON Integration where+ parseJSON = genericParseJSON integrationOptions++instance ToJSON Integration where+ toJSON = genericToJSON integrationOptions-- | CreateFineTuningJob body++-- | Request body for @\/v1\/fine_tuning\/jobs@+data CreateFineTuningJob = CreateFineTuningJob+ { model :: Model+ , training_file :: FileID+ , hyperparameters :: Maybe Hyperparameters+ , suffix :: Maybe Text+ , validation_file :: Maybe FileID+ , integrations :: Maybe (Vector Integration)+ , seed :: Maybe Integer+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Default `CreateFineTuningJob`+_CreateFineTuningJob :: CreateFineTuningJob+_CreateFineTuningJob = CreateFineTuningJob+ { hyperparameters = Nothing+ , suffix = Nothing+ , validation_file = Nothing+ , integrations = Nothing+ , seed = Nothing+ }++-- | The current status of the fine-tuning job+data Status+ = Validating_Files+ | Queued+ | Running+ | Succeeded+ | Failed+ | Cancelled+ deriving stock (Generic, Show)++instance FromJSON Status where+ parseJSON = genericParseJSON aesonOptions++-- | The fine_tuning.job object represents a fine-tuning job that has been+-- created through the API.+data JobObject = JobObject+ { id :: FineTuningJobID+ , created_at :: POSIXTime+ , error :: Maybe Error+ , fine_tuned_model :: Maybe Model+ , finished_at :: Maybe POSIXTime+ , hyperparameters :: Hyperparameters+ , model :: Model+ , object :: Text+ , organization_id :: Text+ , result_files :: Vector FileID+ , status :: Status+ , trained_tokens :: Maybe Natural+ , training_file :: FileID+ , validation_file :: Maybe FileID+ , integrations :: Maybe (Vector Integration)+ , seed :: Integer+ , estimated_finish :: Maybe POSIXTime+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Log level+data Level = Info | Warn | Error+ deriving stock (Generic, Show)++instance FromJSON Level where+ parseJSON = genericParseJSON aesonOptions++-- | Fine-tuning job event object+data EventObject = EventObject+ { id :: Text+ , created_at :: POSIXTime+ , level :: Level+ , message :: Text+ , object :: Text+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Metrics at the step number during the fine-tuning job.+data Metrics = Metrics+ { step :: Double+ , train_loss :: Double+ , train_mean_token_accuracy :: Double+ , valid_loss :: Double+ , valid_mean_token_accuracy :: Double+ , full_valid_loss :: Double+ , full_valid_mean_token_accuracy :: Double+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | The @fine_tuning.job.checkpoint@ object represents a model checkpoint for+-- a fine-tuning job that is ready to use+data CheckpointObject = CheckpointObject+ { id :: Text+ , created_at :: POSIXTime+ , fine_tuned_model_checkpoint :: Text+ , step_number :: Natural+ , metrics :: Metrics+ , fine_tuning_job_id :: FineTuningJobID+ , object :: Text+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "fine_tuning"+ :> "jobs"+ :> ( ReqBody '[JSON] CreateFineTuningJob+ :> Post '[JSON] JobObject+ :<|> QueryParam "after" Text+ :> QueryParam "limit" Natural+ :> Get '[JSON] (ListOf JobObject)+ :<|> Capture "fine_tuning_job_id" FineTuningJobID+ :> "events"+ :> QueryParam "after" Text+ :> QueryParam "limit" Natural+ :> Get '[JSON] (ListOf EventObject)+ :<|> Capture "fine_tuning_job_id" FineTuningJobID+ :> "checkpoints"+ :> QueryParam "after" Text+ :> QueryParam "limit" Natural+ :> Get '[JSON] (ListOf CheckpointObject)+ :<|> Capture "fine_tuning_job_id" FineTuningJobID+ :> Get '[JSON] JobObject+ :<|> Capture "fine_tuning_job_id" FineTuningJobID+ :> "cancel"+ :> Post '[JSON] JobObject+ )
+ src/OpenAI/V1/Images.hs view
@@ -0,0 +1,14 @@+-- | @\/v1\/images@+module OpenAI.V1.Images+ ( -- * Servant+ API+ ) where++import OpenAI.Prelude++import qualified OpenAI.V1.Images.Generations as Generations+import qualified OpenAI.V1.Images.Edits as Edits+import qualified OpenAI.V1.Images.Variations as Variations++-- | Servant API+type API = "images" :> (Generations.API :<|> Edits.API :<|> Variations.API)
+ src/OpenAI/V1/Images/Edits.hs view
@@ -0,0 +1,76 @@+-- | @\/v1\/images\/edits@+module OpenAI.V1.Images.Edits+ ( -- * Main types+ CreateImageEdit(..)+ , _CreateImageEdit+ -- * Other types+ , ResponseFormat(..)+ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Images.Image+import OpenAI.V1.Images.ResponseFormat+import OpenAI.V1.ListOf+import OpenAI.V1.Models (Model(..))++import qualified Data.Text as Text++-- | Request body for @\/v1\/images\/edits@+data CreateImageEdit = CreateImageEdit+ { image :: FilePath+ , prompt :: Text+ , mask :: Maybe FilePath+ , model :: Maybe Model+ , n :: Maybe Natural+ , size :: Maybe Text+ , response_format :: Maybe ResponseFormat+ , user :: Maybe Text+ } deriving stock (Generic, Show)++-- | Default `CreateImageEdit`+_CreateImageEdit :: CreateImageEdit+_CreateImageEdit = CreateImageEdit+ { mask = Nothing+ , model = Nothing+ , n = Nothing+ , size = Nothing+ , response_format = Nothing+ , user = Nothing+ }++instance ToMultipart Tmp CreateImageEdit where+ toMultipart CreateImageEdit{ ..} = MultipartData{..}+ where+ inputs =+ input "prompt" prompt+ <> foldMap (input "model" . text) model+ <> foldMap (input "n" . renderIntegral) n+ <> foldMap (input "size") size+ <> foldMap (input "response_format" . toUrlPiece) response_format+ <> foldMap (input "user") user++ files = file0 : files1+ where+ file0 = FileData{..}+ where+ fdInputName = "image"+ fdFileName = Text.pack image+ fdFileCType = "image/" <> getExtension image+ fdPayload = image++ files1 = case mask of+ Nothing -> [ ]+ Just m -> [ FileData{..} ]+ where+ fdInputName = "mask"+ fdFileName = Text.pack m+ fdFileCType = "image/" <> getExtension m+ fdPayload = m++-- | Servant API+type API =+ "edits"+ :> MultipartForm Tmp CreateImageEdit+ :> Post '[JSON] (ListOf ImageObject)
+ src/OpenAI/V1/Images/Generations.hs view
@@ -0,0 +1,64 @@+-- | @\/v1\/images\/generations@+module OpenAI.V1.Images.Generations+ ( -- * Main types+ CreateImage(..)+ , _CreateImage+ -- * Other types+ , Quality(..)+ , Style(..)+ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Images.Image+import OpenAI.V1.Images.ResponseFormat+import OpenAI.V1.ListOf+import OpenAI.V1.Models (Model)++-- | The quality of the image that will be generated+data Quality = Standard | HD+ deriving stock (Generic, Show)++instance ToJSON Quality where+ toJSON = genericToJSON aesonOptions++-- | The style of the generated images+data Style = Vivid | Natural+ deriving stock (Generic, Show)++instance ToJSON Style where+ toJSON = genericToJSON aesonOptions++-- | Requesty body for @\/v1/images/generations@+data CreateImage = CreateImage+ { prompt :: Text+ , model :: Maybe Model+ , n :: Maybe Natural+ , quality :: Maybe Quality+ , response_format :: Maybe ResponseFormat+ , size :: Maybe Text+ , style :: Maybe Style+ , user :: Maybe Text+ } deriving stock (Generic, Show)++-- | Default `CreateImage`+_CreateImage :: CreateImage+_CreateImage = CreateImage+ { model = Nothing+ , n = Nothing+ , quality = Nothing+ , response_format = Nothing+ , size = Nothing+ , style = Nothing+ , user = Nothing+ }++instance ToJSON CreateImage where+ toJSON = genericToJSON aesonOptions++-- | Servant API+type API =+ "generations"+ :> ReqBody '[JSON] CreateImage+ :> Post '[JSON] (ListOf ImageObject)
+ src/OpenAI/V1/Images/Image.hs view
@@ -0,0 +1,15 @@+-- | The image object+module OpenAI.V1.Images.Image+ ( -- * Types+ ImageObject(..)+ ) where++import OpenAI.Prelude++-- | Represents the url or the content of an image generated by the OpenAI API+data ImageObject = ImageObject+ { b64_json :: Maybe Text+ , url :: Maybe Text+ , revised_prompt :: Maybe Text+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)
+ src/OpenAI/V1/Images/ResponseFormat.hs view
@@ -0,0 +1,18 @@+-- | The response format+module OpenAI.V1.Images.ResponseFormat+ ( -- * Types+ ResponseFormat(..)+ ) where++import OpenAI.Prelude++-- | The format in which the generated images are returned+data ResponseFormat = URL | B64_JSON+ deriving stock (Generic, Show)++instance ToJSON ResponseFormat where+ toJSON = genericToJSON aesonOptions++instance ToHttpApiData ResponseFormat where+ toUrlPiece URL = "url"+ toUrlPiece B64_JSON = "b64_json"
+ src/OpenAI/V1/Images/Variations.hs view
@@ -0,0 +1,59 @@+-- | @\/v1\/images\/variations@+module OpenAI.V1.Images.Variations+ ( -- * Main types+ CreateImageVariation(..)+ , _CreateImageVariation+ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Images.Image+import OpenAI.V1.Images.ResponseFormat+import OpenAI.V1.ListOf+import OpenAI.V1.Models (Model(..))++import qualified Data.Text as Text++-- | Request body for @\/v1\/images\/variations@+data CreateImageVariation = CreateImageVariation+ { image :: FilePath+ , model :: Maybe Model+ , n :: Maybe Natural+ , response_format :: Maybe ResponseFormat+ , size :: Maybe Text+ , user :: Maybe Text+ } deriving stock (Generic, Show)++-- | Default `CreateImageVariation`+_CreateImageVariation :: CreateImageVariation+_CreateImageVariation = CreateImageVariation+ { model = Nothing+ , n = Nothing+ , response_format = Nothing+ , size = Nothing+ , user = Nothing+ }++instance ToMultipart Tmp CreateImageVariation where+ toMultipart CreateImageVariation{..} = MultipartData{..}+ where+ inputs =+ foldMap (input "model" . text) model+ <> foldMap (input "n" . renderIntegral) n+ <> foldMap (input "response_format" . toUrlPiece) response_format+ <> foldMap (input "size") size+ <> foldMap (input "user") user++ files = [ FileData{..} ]+ where+ fdInputName = "image"+ fdFileName = Text.pack image+ fdFileCType = "image/" <> getExtension image+ fdPayload = image++-- | Servant API+type API =+ "variations"+ :> MultipartForm Tmp CreateImageVariation+ :> Post '[JSON] (ListOf ImageObject)
+ src/OpenAI/V1/ListOf.hs view
@@ -0,0 +1,16 @@+-- | The `ListOf` type constructor+module OpenAI.V1.ListOf+ ( -- * Types+ ListOf(..)+ ) where++import OpenAI.Prelude++-- | Whenever OpenAI says that an API endpoint returns a list of items, what+-- they actually mean is that it returns that list wrapped inside of the+-- following object+data ListOf a = List{ data_ :: Vector a }+ deriving stock (Generic, Show)++instance FromJSON a => FromJSON (ListOf a) where+ parseJSON = genericParseJSON aesonOptions
+ src/OpenAI/V1/Message.hs view
@@ -0,0 +1,78 @@+-- | The `Message` type+module OpenAI.V1.Message+ ( -- * Main types+ Message(..)++ -- * Other types+ , ImageFile(..)+ , ImageURL(..)+ , Content(..)+ , Attachment(..)+ ) where++import OpenAI.Prelude+import OpenAI.V1.AutoOr+import OpenAI.V1.Files (FileID)+import OpenAI.V1.Tool++-- | References an image File in the content of a message+data ImageFile = ImageFile{ file_id :: FileID, detail :: Maybe (AutoOr Text) }+ deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | References an image URL in the content of a message+data ImageURL = ImageURL+ { image_url :: Text+ , detail :: Maybe (AutoOr Text)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | Message content+data Content text+ = Image_File{ image_file :: ImageFile }+ | Image_URL{ image_url :: ImageURL }+ | Text{ text :: text }+ deriving stock (Generic, Show)++contentOptions :: Options+contentOptions = aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True+ }++instance FromJSON text => FromJSON (Content text) where+ parseJSON = genericParseJSON contentOptions++instance ToJSON text => ToJSON (Content text) where+ toJSON = genericToJSON contentOptions++instance IsString text => IsString (Content text) where+ fromString string = Text{ text = fromString string }++-- | A file attached to the message, and the tools it should be added to+data Attachment = Attachment{ file_id :: FileID, tools :: Maybe (Vector Tool) }+ deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | A message+data Message+ = User+ { content :: Vector (Content Text)+ , attachments :: Maybe (Vector Attachment)+ , metadata :: Maybe (Map Text Text)+ }+ | Assistant+ { content :: Vector (Content Text)+ , attachments :: Maybe (Vector Attachment)+ , metadata :: Maybe (Map Text Text)+ }+ deriving stock (Generic, Show)++instance ToJSON Message where+ toJSON = genericToJSON aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "role", contentsFieldName = "" }+ , tagSingleConstructors = True+ }
+ src/OpenAI/V1/Models.hs view
@@ -0,0 +1,36 @@+-- | @\/v1\/models@+module OpenAI.V1.Models+ ( -- * Main types+ Model(..)+ , ModelObject(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.DeletionStatus+import OpenAI.V1.ListOf++-- | Model+newtype Model = Model{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | Describes an OpenAI model offering that can be used with the API+data ModelObject = ModelObject+ { id :: Model+ , created :: POSIXTime+ , object :: Text+ , owned_by :: Text+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "models"+ :> ( Get '[JSON] (ListOf ModelObject)+ :<|> Capture "model" Model+ :> Get '[JSON] ModelObject+ :<|> Capture "model" Model+ :> Delete '[JSON] DeletionStatus+ )
+ src/OpenAI/V1/Moderations.hs view
@@ -0,0 +1,60 @@+-- | @\/v1\/moderations@+module OpenAI.V1.Moderations+ ( -- * Main types+ CreateModeration(..)+ , _CreateModeration+ , Moderation(..)++ -- * Other types+ , InputType(..)+ , Result(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Models (Model)++-- | Request body for @\/v1\/moderations@+data CreateModeration = CreateModeration+ { input :: Text+ , model :: Maybe Model+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Default `CreateModeration`+_CreateModeration :: CreateModeration+_CreateModeration = CreateModeration+ { model = Nothing+ }++-- | The input type that the score applies to+data InputType = Text | Image+ deriving stock (Generic, Show)++instance FromJSON InputType where+ parseJSON = genericParseJSON aesonOptions++-- | A moderation result+data Result = Result+ { flagged :: Bool+ , categories :: Map Text Bool+ , category_scores :: Map Text Double+ , category_applied_input_types :: Maybe (Map Text InputType)+ -- According to the OpenAPI spec the `category_applied_input_types`+ -- field is required but their actual implementation omits this field.+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Represents if a given text input is potentially harmful.+data Moderation = Moderation+ { id :: Text+ , model :: Model+ , results :: Vector Result+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "moderations" :> ReqBody '[JSON] CreateModeration :> Post '[JSON] Moderation
+ src/OpenAI/V1/Order.hs view
@@ -0,0 +1,14 @@+-- | The `Order` type+module OpenAI.V1.Order+ ( -- * Types+ Order(..)+ ) where++import OpenAI.Prelude++-- | Sort order by the @created_at@ timestamp of the objects+data Order = Desc | Asc++instance ToHttpApiData Order where+ toUrlPiece Desc = "desc"+ toUrlPiece Asc = "asc"
+ src/OpenAI/V1/ResponseFormat.hs view
@@ -0,0 +1,43 @@+-- | The `ResponseFormat` type+module OpenAI.V1.ResponseFormat+ ( -- * Types+ ResponseFormat(..)+ , JSONSchema(..)+ ) where++import OpenAI.Prelude++-- | An object specifying the format that the model must output+data ResponseFormat+ = ResponseFormat_Text+ | JSON_Object+ | JSON_Schema{ json_schema :: JSONSchema }+ deriving stock (Generic, Show)++responseFormatOptions :: Options+responseFormatOptions = aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True++ , constructorTagModifier = stripPrefix "ResponseFormat_"+ }++instance FromJSON ResponseFormat where+ parseJSON = genericParseJSON responseFormatOptions++instance ToJSON ResponseFormat where+ toJSON = genericToJSON responseFormatOptions++-- | Setting to { "type": "json_schema", "json_schema": {...} } enables+-- Structured Outputs which ensures the model will match your supplied JSON+-- schema. Learn more in the+-- [Structured Outputs](https://platform.openai.com/docs/guides/structured-outputs) guide+data JSONSchema = JSONSchema+ { description :: Maybe Text+ , name :: Text+ , schema :: Maybe Value+ , strict :: Maybe Bool+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)
+ src/OpenAI/V1/Threads.hs view
@@ -0,0 +1,86 @@+-- | @\/v1\/threads@+module OpenAI.V1.Threads+ ( -- Main types+ ThreadID(..)+ , Thread(..)+ , _Thread+ , ModifyThread(..)+ , _ModifyThread+ , Message(..)+ , Content(..)+ , ThreadObject(..)++ -- * Other types+ , ImageURL(..)+ , ImageFile(..)+ , Attachment(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.DeletionStatus+import OpenAI.V1.Message+import OpenAI.V1.ToolResources++-- | Thread ID+newtype ThreadID = ThreadID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | Request body for @\/v1\/threads@+data Thread = Thread+ { messages :: Maybe (Vector Message)+ , tool_resources :: Maybe ToolResources+ , metadata :: Maybe (Map Text Text)+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Default `Thread`+_Thread :: Thread+_Thread = Thread+ { messages = Nothing+ , tool_resources = Nothing+ , metadata = Nothing+ }++-- | Request body for @\/v1\/threads\/:thread_id@+data ModifyThread = ModifyThread+ { tool_resources :: Maybe ToolResources+ , metadata :: Maybe (Map Text Text)+ } deriving stock (Generic, Show)++instance ToJSON ModifyThread where+ toJSON = genericToJSON aesonOptions++-- | Default `ModifyThread`+_ModifyThread :: ModifyThread+_ModifyThread = ModifyThread+ { tool_resources = Nothing+ , metadata = Nothing+ }++-- | Represents a thread that contains messages+data ThreadObject = ThreadObject+ { id :: ThreadID+ , object :: Text+ , created_at :: POSIXTime+ , tool_resources :: Maybe ToolResources+ , metadata :: Maybe (Map Text Text)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "threads"+ :> Header' '[Required, Strict] "OpenAI-Beta" Text+ :> ( ReqBody '[JSON] Thread+ :> Post '[JSON] ThreadObject+ :<|> Capture "thread_id" ThreadID+ :> Get '[JSON] ThreadObject+ :<|> Capture "thread_id" ThreadID+ :> ReqBody '[JSON] ModifyThread+ :> Post '[JSON] ThreadObject+ :<|> Capture "thread_id" ThreadID+ :> Delete '[JSON] DeletionStatus+ )
+ src/OpenAI/V1/Threads/Messages.hs view
@@ -0,0 +1,145 @@+-- | @\/v1\/threads/:thread_id/messages@+module OpenAI.V1.Threads.Messages+ ( -- * Main types+ MessageID(..)+ , Message(..)+ , ModifyMessage(..)+ , _ModifyMessage+ , MessageObject(..)++ -- * Other types+ , Status(..)+ , IncompleteDetails(..)+ , File(..)+ , Annotation(..)+ , TextObject(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Assistants (AssistantID)+import OpenAI.V1.DeletionStatus+import OpenAI.V1.Files (FileID)+import OpenAI.V1.ListOf+import OpenAI.V1.Message+import OpenAI.V1.Threads (ThreadID)+import OpenAI.V1.Threads.Runs (RunID)++-- | Message ID+newtype MessageID = MessageID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | Request body for @\/v1\/threads\/:thread_id\/messages\/:message_id@+data ModifyMessage = ModifyMessage+ { metadata :: Maybe (Map Text Text)+ } deriving stock (Generic, Show)++instance ToJSON ModifyMessage where+ toJSON = genericToJSON aesonOptions++-- | Default `ModifyMessage`+_ModifyMessage :: ModifyMessage+_ModifyMessage = ModifyMessage+ { metadata = Nothing+ }++-- | The status of the message+data Status = In_Progress | Incomplete | Completed+ deriving stock (Generic, Show)++instance FromJSON Status where+ parseJSON = genericParseJSON aesonOptions++-- | On an incomplete message, details about why the message is incomplete.+data IncompleteDetails = IncompleteDetails+ { reason :: Text+ } deriving (Generic, Show)+ deriving anyclass (FromJSON)++-- | File+data File = File+ { file_id :: FileID+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | An annotation+data Annotation+ = File_Citation+ { text :: Text+ , file_citation :: File+ , start_index :: Natural+ , end_index :: Natural+ }+ | File_Path+ { text :: Text+ , file_path :: File+ , start_index :: Natural+ , end_index :: Natural+ }+ deriving stock (Generic, Show)++instance FromJSON Annotation where+ parseJSON = genericParseJSON aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True+ }++-- | The text content that is part of a message.+data TextObject = TextObject+ { value :: Text+ , annotations :: Vector Annotation+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++instance IsString TextObject where+ fromString string =+ TextObject{ value = fromString string, annotations = [] }++-- | Represents a message within a thread.+data MessageObject = MessageObject+ { id :: MessageID+ , object :: Text+ , created_at :: POSIXTime+ , thread_id :: ThreadID+ , status :: Maybe Status+ , incomplete_details :: Maybe IncompleteDetails+ , completed_at :: Maybe POSIXTime+ , incomplete_at :: Maybe POSIXTime+ , role :: Text+ , content :: Vector (Content TextObject)+ , assistant_id :: Maybe AssistantID+ , run_id :: Maybe RunID+ , attachments :: Maybe (Vector Attachment)+ , metadata :: Map Text Text+ } deriving (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ Header' '[Required, Strict] "OpenAI-Beta" Text+ :> "threads"+ :> ( Capture "thread_id" ThreadID+ :> "messages"+ :> ReqBody '[JSON] Message+ :> Post '[JSON] MessageObject+ :<|> Capture "thread_id" ThreadID+ :> "messages"+ :> Get '[JSON] (ListOf MessageObject)+ :<|> Capture "thread_id" ThreadID+ :> "messages"+ :> Capture "message_id" MessageID+ :> Get '[JSON] MessageObject+ :<|> Capture "thread_id" ThreadID+ :> "messages"+ :> Capture "message_id" MessageID+ :> ReqBody '[JSON] ModifyMessage+ :> Post '[JSON] MessageObject+ :<|> Capture "thread_id" ThreadID+ :> "messages"+ :> Capture "message_id" MessageID+ :> Delete '[JSON] DeletionStatus+ )
+ src/OpenAI/V1/Threads/Runs.hs view
@@ -0,0 +1,294 @@+-- | @\/v1\/threads\/:thread_id\/runs@+module OpenAI.V1.Threads.Runs+ ( -- * Main types+ RunID(..)+ , CreateRun(..)+ , _CreateRun+ , CreateThreadAndRun(..)+ , _CreateThreadAndRun+ , ModifyRun(..)+ , _ModifyRun+ , SubmitToolOutputsToRun(..)+ , _SubmitToolOutputsToRun+ , RunObject(..)++ -- * Other types+ , TruncationStrategy(..)+ , SubmitToolOutputs(..)+ , RequiredAction(..)+ , IncompleteDetails(..)+ , ToolOutput(..)+ , Status(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Assistants (AssistantID)+import OpenAI.V1.AutoOr+import OpenAI.V1.Error+import OpenAI.V1.ListOf+import OpenAI.V1.Message+import OpenAI.V1.Models (Model)+import OpenAI.V1.Order+import OpenAI.V1.ResponseFormat+import OpenAI.V1.Threads (Thread)+import OpenAI.V1.Tool+import OpenAI.V1.ToolCall+import OpenAI.V1.ToolResources+import OpenAI.V1.Threads (ThreadID)+import OpenAI.V1.Usage++-- | Run ID+newtype RunID = RunID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | Controls for how a thread will be truncated prior to the run+data TruncationStrategy+ = Auto+ | Last_Messages{ last_messages :: Maybe Natural }+ deriving stock (Generic, Show)++truncationStrategyOptions :: Options+truncationStrategyOptions = aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True+ }++instance FromJSON TruncationStrategy where+ parseJSON = genericParseJSON truncationStrategyOptions++instance ToJSON TruncationStrategy where+ toJSON = genericToJSON truncationStrategyOptions++-- | Request body for @\/v1\/threads\/:thread_id\/runs@+data CreateRun = CreateRun+ { assistant_id :: AssistantID+ , model :: Maybe Model+ , instructions :: Maybe Text+ , additional_instructions :: Maybe Text+ , additional_messages :: Maybe (Vector Message)+ , tools :: Maybe (Vector Tool)+ , metadata :: Maybe (Map Text Text)+ , temperature :: Maybe Double+ , top_p :: Maybe Double+ , max_prompt_tokens :: Maybe Natural+ , max_completion_tokens :: Maybe Natural+ , truncation_strategy :: Maybe TruncationStrategy+ , tool_choice :: Maybe ToolChoice+ , parallel_tool_calls :: Maybe Bool+ , response_format :: Maybe (AutoOr ResponseFormat)+ } deriving stock (Generic, Show)++instance ToJSON CreateRun where+ toJSON = genericToJSON aesonOptions++-- | Default `CreateRun`+_CreateRun :: CreateRun+_CreateRun = CreateRun+ { model = Nothing+ , instructions = Nothing+ , additional_instructions = Nothing+ , additional_messages = Nothing+ , tools = Nothing+ , metadata = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , max_prompt_tokens = Nothing+ , max_completion_tokens = Nothing+ , truncation_strategy = Nothing+ , tool_choice = Nothing+ , parallel_tool_calls = Nothing+ , response_format = Nothing+ }++-- | Request body for @\/v1\/threads\/runs@+data CreateThreadAndRun = CreateThreadAndRun+ { assistant_id :: AssistantID+ , thread :: Maybe Thread+ , model :: Maybe Model+ , instructions :: Maybe Text+ , tools :: Maybe (Vector Tool)+ , toolResources :: Maybe ToolResources+ , metadata :: Maybe (Map Text Text)+ , temperature :: Maybe Double+ , top_p :: Maybe Double+ , max_prompt_tokens :: Maybe Natural+ , max_completion_tokens :: Maybe Natural+ , truncation_strategy :: Maybe TruncationStrategy+ , tool_choice :: Maybe ToolChoice+ , parallel_tool_calls :: Maybe Bool+ , response_format :: Maybe (AutoOr ResponseFormat)+ } deriving stock (Generic, Show)++instance ToJSON CreateThreadAndRun where+ toJSON = genericToJSON aesonOptions++-- | Default `CreateThreadAndRun`+_CreateThreadAndRun :: CreateThreadAndRun+_CreateThreadAndRun = CreateThreadAndRun+ { thread = Nothing+ , model = Nothing+ , instructions = Nothing+ , tools = Nothing+ , toolResources = Nothing+ , metadata = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , max_prompt_tokens = Nothing+ , max_completion_tokens = Nothing+ , truncation_strategy = Nothing+ , tool_choice = Nothing+ , parallel_tool_calls = Nothing+ , response_format = Nothing+ }++-- | Details on the tool outputs needed for this run to continue.+data SubmitToolOutputs = SubmitToolOutputs+ { tool_calls :: Vector ToolCall+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | The status of the run+data Status+ = Queued+ | In_Progress+ | Requires_Action+ | Cancelling+ | Cancelled+ | Failed+ | Completed+ | Incomplete+ | Expired+ deriving stock (Generic, Show)++instance FromJSON Status where+ parseJSON = genericParseJSON aesonOptions++-- | Details on the action required to continue the run+data RequiredAction = RequiredAction_Submit_Tool_Outputs+ { submit_tool_outputs :: SubmitToolOutputs+ } deriving stock (Generic, Show)++instance FromJSON RequiredAction where+ parseJSON = genericParseJSON aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type" }++ , tagSingleConstructors = True++ , constructorTagModifier = stripPrefix "RequiredAction_"+ }++-- | Details on why the run is incomplete+data IncompleteDetails = IncompleteDetails+ { reason :: Text+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Represents an execution run on a thread.+data RunObject = RunObject+ { id :: RunID+ , object :: Text+ , created_at :: POSIXTime+ , thread_id :: ThreadID+ , assistant_id :: AssistantID+ , status :: Status+ , required_action :: Maybe RequiredAction+ , last_error :: Maybe Error+ , expires_at :: Maybe POSIXTime+ , started_at :: Maybe POSIXTime+ , cancelled_at :: Maybe POSIXTime+ , failed_at :: Maybe POSIXTime+ , completed_at :: Maybe POSIXTime+ , incomplete_details :: Maybe IncompleteDetails+ , model :: Model+ , instructions :: Maybe Text+ , tools :: Vector Tool+ , metadata :: Map Text Text+ , usage :: Maybe (Usage (Maybe Void) (Maybe Void))+ , temperature :: Maybe Double+ , top_p :: Maybe Double+ , max_prompt_tokens :: Maybe Natural+ , max_completion_tokens :: Maybe Natural+ , truncation_strategy :: Maybe TruncationStrategy+ , tool_choice :: ToolChoice+ , parallel_tool_calls :: Bool+ , response_format :: AutoOr ResponseFormat+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Request body for @\/v1\/threads\/:thread_id\/runs\/:run_id@+data ModifyRun = ModifyRun+ { metadata :: Maybe (Map Text Text)+ } deriving stock (Generic, Show)++instance ToJSON ModifyRun where+ toJSON = genericToJSON aesonOptions++-- | Default `ModifyRun`+_ModifyRun :: ModifyRun+_ModifyRun = ModifyRun{ }++-- | A tool for which the output is being submitted+data ToolOutput = ToolOutput+ { tool_call_id :: Maybe Text+ , output :: Text+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Request body for @\/v1\/threads\/:thread_id\/runs\/:run_id\/submit_tool_outputs@+data SubmitToolOutputsToRun = SubmitToolOutputsToRun+ { tool_outputs :: Vector ToolOutput+ } deriving stock (Generic, Show)++instance ToJSON SubmitToolOutputsToRun where+ toJSON = genericToJSON aesonOptions++-- | Default implementation of `SubmitToolOutputsToRun`+_SubmitToolOutputsToRun :: SubmitToolOutputsToRun+_SubmitToolOutputsToRun = SubmitToolOutputsToRun{ }++-- | Servant API+type API =+ "threads"+ :> Header' '[Required, Strict] "OpenAI-Beta" Text+ :> ( Capture "thread_id" ThreadID+ :> "runs"+ :> QueryParam "include[]" Text+ :> ReqBody '[JSON] CreateRun+ :> Post '[JSON] RunObject+ :<|> "runs"+ :> ReqBody '[JSON] CreateThreadAndRun+ :> Post '[JSON] RunObject+ :<|> Capture "thread_id" ThreadID+ :> "runs"+ :> QueryParam "limit" Natural+ :> QueryParam "order" Order+ :> QueryParam "after" Text+ :> QueryParam "before" Text+ :> Get '[JSON] (ListOf RunObject)+ :<|> Capture "thread_id" ThreadID+ :> "runs"+ :> Capture "run_id" RunID+ :> Get '[JSON] RunObject+ :<|> Capture "thread_id" ThreadID+ :> "runs"+ :> Capture "run_id" RunID+ :> ReqBody '[JSON] ModifyRun+ :> Post '[JSON] RunObject+ :<|> Capture "thread_id" ThreadID+ :> "runs"+ :> Capture "run_id" RunID+ :> "submit_tool_outputs"+ :> ReqBody '[JSON] SubmitToolOutputsToRun+ :> Post '[JSON] RunObject+ :<|> Capture "thread_id" ThreadID+ :> "runs"+ :> Capture "run_id" RunID+ :> "cancel"+ :> Post '[JSON] RunObject+ )
+ src/OpenAI/V1/Threads/Runs/Steps.hs view
@@ -0,0 +1,184 @@+-- | @\/v1\/threads\/:thread_id\/runs\/:run_id\/steps@+module OpenAI.V1.Threads.Runs.Steps+ ( -- * Main types+ StepID(..)+ , RunStepObject(..)++ -- * Other types+ , Status(..)+ , Image(..)+ , Output(..)+ , CodeInterpreter(..)+ , RankingOptions(..)+ , Content(..)+ , Result(..)+ , FileSearch(..)+ , Function(..)+ , ToolCall(..)+ , StepDetails(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Assistants (AssistantID)+import OpenAI.V1.Error+import OpenAI.V1.Files (FileID)+import OpenAI.V1.ListOf+import OpenAI.V1.Threads.Messages (MessageID)+import OpenAI.V1.Order+import OpenAI.V1.Threads (ThreadID)+import OpenAI.V1.Threads.Runs (RunID)+import OpenAI.V1.Usage++-- | Step ID+newtype StepID = StepID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | The status of the run step+data Status = In_Progress | Cancelled | Failed | Completed | Expired+ deriving stock (Generic, Show)++instance FromJSON Status where+ parseJSON = genericParseJSON aesonOptions++-- | Code Interpreter image output+data Image = Image{ file_id :: FileID }+ deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | An output from the Code Interpreter tool call+data Output = Output_Logs{ logs :: Text } | Output_Image{ image :: Image }+ deriving stock (Generic, Show)++instance FromJSON Output where+ parseJSON = genericParseJSON aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True++ , constructorTagModifier = stripPrefix "Output_"+ }++-- | A Code Interpreter tool call+data CodeInterpreter = CodeInterpreter+ { input :: Text+ , outputs :: Vector Output+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | The ranking options for the file search.+data RankingOptions = RankingOptions+ { ranker :: Text+ , score_threshold :: Double+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | The content of the result that was found+data Content = Content+ { type_ :: Text+ , text :: Text+ } deriving stock (Generic, Show)++instance FromJSON Content where+ parseJSON = genericParseJSON aesonOptions++-- | Result of the file search+data Result = Result+ { file_id :: FileID+ , file_name :: Text+ , score :: Double+ , content :: Vector Content+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | A File Search tool call+data FileSearch = FileSearch+ { ranking_options :: RankingOptions+ , results :: Vector Result+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | The definition of the function that was called+data Function = Function+ { name :: Text+ , arguments :: Text+ , output :: Maybe Text+ } deriving (Generic, Show)+ deriving anyclass (FromJSON)++-- | A tool call the run step was involved in+data ToolCall+ = ToolCall_Code_Interpreter { id :: Text, code_interpreter :: CodeInterpreter }+ | ToolCall_File_Search { id :: Text, file_search :: Map Text FileSearch }+ | ToolCall_Function { id :: Text, function :: Function }+ deriving stock (Generic, Show)++instance FromJSON ToolCall where+ parseJSON = genericParseJSON aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True++ , constructorTagModifier = stripPrefix "ToolCall_"+ }++-- | The details of the run step+data StepDetails+ = Message_Creation{ message_id :: MessageID }+ | Tool_Calls{ tool_calls :: Vector ToolCall }+ deriving stock (Generic, Show)++instance FromJSON StepDetails where+ parseJSON = genericParseJSON aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True+ }++-- | Represents a step in execution of a run.+data RunStepObject = RunStepObject+ { id :: StepID+ , object :: Text+ , created_at :: POSIXTime+ , assistant_id :: AssistantID+ , thread_id :: ThreadID+ , run_id :: RunID+ , status :: Status+ , step_details :: StepDetails+ , last_error :: Maybe Error+ , expired_at :: Maybe POSIXTime+ , cancelled_at :: Maybe POSIXTime+ , failed_at :: Maybe POSIXTime+ , completed_at :: Maybe POSIXTime+ , metadata :: Map Text Text+ , usage :: Maybe (Usage CompletionTokensDetails PromptTokensDetails)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "threads"+ :> Header' '[Required, Strict] "OpenAI-Beta" Text+ :> ( Capture "thread_id" ThreadID+ :> "runs"+ :> Capture "run_id" RunID+ :> "steps"+ :> QueryParam "limit" Natural+ :> QueryParam "order" Order+ :> QueryParam "after" Text+ :> QueryParam "before" Text+ :> QueryParam "include[]" Text+ :> Get '[JSON] (ListOf RunStepObject)+ :<|> Capture "thread_id" ThreadID+ :> "runs"+ :> Capture "run_id" RunID+ :> "steps"+ :> Capture "step_id" StepID+ :> QueryParam "include[]" Text+ :> Get '[JSON] RunStepObject+ )
+ src/OpenAI/V1/Tool.hs view
@@ -0,0 +1,77 @@+-- | The `Tool` type+module OpenAI.V1.Tool+ ( -- * Types+ Tool(..)+ , RankingOptions(..)+ , FileSearch(..)+ , Function(..)+ , ToolChoice(..)+ ) where++import OpenAI.Prelude++-- | The ranking options for the file search+data RankingOptions = RankingOptions+ { ranker :: Maybe Text+ , score_threshold :: Double+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | Overrides for the file search tool+data FileSearch = FileSearch+ { max_num_results :: Maybe Natural+ , ranking_options :: Maybe RankingOptions+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | The Function tool+data Function = Function+ { description :: Maybe Text+ , name :: Text+ , parameters :: Maybe Value+ , strict :: Maybe Bool+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | A tool enabled on the assistant+data Tool+ = Tool_Code_Interpreter+ | Tool_File_Search{ file_search :: FileSearch }+ | Tool_Function{ function :: Function }+ deriving stock (Generic, Show)++toolOptions :: Options+toolOptions = aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True++ , constructorTagModifier = stripPrefix "Tool_"+ }++instance FromJSON Tool where+ parseJSON = genericParseJSON toolOptions++instance ToJSON Tool where+ toJSON = genericToJSON toolOptions++-- | Controls which (if any) tool is called by the model+data ToolChoice+ = ToolChoiceNone+ | ToolChoiceAuto+ | ToolChoiceRequired+ | ToolChoiceTool Tool+ deriving stock (Generic, Show)++instance FromJSON ToolChoice where+ parseJSON "none" = pure ToolChoiceNone+ parseJSON "auto" = pure ToolChoiceAuto+ parseJSON "required" = pure ToolChoiceRequired+ parseJSON other = fmap ToolChoiceTool (parseJSON other)++instance ToJSON ToolChoice where+ toJSON ToolChoiceNone = "none"+ toJSON ToolChoiceAuto = "auto"+ toJSON ToolChoiceRequired = "required"+ toJSON (ToolChoiceTool tool) = toJSON tool
+ src/OpenAI/V1/ToolCall.hs view
@@ -0,0 +1,35 @@+-- | The `ToolCall` type+module OpenAI.V1.ToolCall+ ( -- * Types+ ToolCall(..)+ , Function(..)+ ) where++import OpenAI.Prelude++-- | A called function+data Function = Function{ name :: Text, arguments :: Text }+ deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | Tools called by the model+data ToolCall = ToolCall_Function+ { id :: Text+ , function :: Function+ } deriving stock (Generic, Show)++toolCallOptions :: Options+toolCallOptions = aesonOptions+ { sumEncoding =+ TaggedObject{ tagFieldName = "type", contentsFieldName = "" }++ , tagSingleConstructors = True++ , constructorTagModifier = stripPrefix "ToolCall_"+ }++instance ToJSON ToolCall where+ toJSON = genericToJSON toolCallOptions++instance FromJSON ToolCall where+ parseJSON = genericParseJSON toolCallOptions
+ src/OpenAI/V1/ToolResources.hs view
@@ -0,0 +1,44 @@+-- | The `ToolResources` type+module OpenAI.V1.ToolResources+ ( -- * Main types+ ToolResources(..)++ -- * Other types+ , CodeInterpreterResources(..)+ , Static(..)+ , VectorStore(..)+ , FileSearchResources(..)+ ) where++import OpenAI.Prelude+import OpenAI.V1.AutoOr+import OpenAI.V1.ChunkingStrategy+import OpenAI.V1.Files (FileID)++-- | Resources for the code search tool+data CodeInterpreterResources = CodeInterpreterResources+ { file_ids :: Maybe (Vector FileID)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | A helper to create a vector store with file_ids and attach it to this+-- assistant+data VectorStore = VectorStore+ { file_ids :: Maybe (Vector FileID)+ , chunking_strategy :: Maybe (AutoOr ChunkingStrategy)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | Resources for the file search tool+data FileSearchResources = FileSearchResources+ { vector_store_ids :: Maybe (Vector FileID)+ , vector_stores :: Maybe (Vector VectorStore)+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | A set of resources that are used by the assistant's tools+data ToolResources = ToolResources+ { code_interpreter :: Maybe CodeInterpreterResources+ , file_search :: Maybe FileSearchResources+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)
+ src/OpenAI/V1/Uploads.hs view
@@ -0,0 +1,136 @@+-- | @\/v1\/uploads@+{-# LANGUAGE InstanceSigs #-}+module OpenAI.V1.Uploads+ ( -- * Main types+ UploadID(..)+ , CreateUpload(..)+ , _CreateUpload+ , AddUploadPart(..)+ , _AddUploadPart+ , CompleteUpload(..)+ , _CompleteUpload+ , UploadObject(..)+ , PartObject(..)+ -- * Other types+ , Status(..)+ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.Files (FileObject, Purpose)++import qualified Data.Text as Text++-- | Upload ID+newtype UploadID = UploadID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | Request body for @\/v1\/uploads@+data CreateUpload = CreateUpload+ { filename :: Text+ , purpose :: Purpose+ , bytes :: Natural+ , mime_type :: Text+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Default `CreateUpload`+_CreateUpload :: CreateUpload+_CreateUpload = CreateUpload{ }++-- | Request body for @\/v1\/uploads\/:upload_id\/parts@+data AddUploadPart = AddUploadPart{ data_ :: FilePath }++-- | Default `AddUploadPart`+_AddUploadPart :: AddUploadPart+_AddUploadPart = AddUploadPart{ }++instance ToMultipart Tmp AddUploadPart where+ toMultipart AddUploadPart{..} = MultipartData{..}+ where+ inputs = mempty++ files = [ FileData{..} ]+ where+ fdInputName = "data"+ fdFileName = Text.pack data_+ fdFileCType = "application/octet-stream"+ fdPayload = data_++-- | Request body for @\/v1\/uploads\/:upload_id\/complete@+data CompleteUpload = CompleteUpload+ { part_ids :: Vector Text+ , md5 :: Maybe Text+ } deriving stock (Generic, Show)++-- | Default `CompleteUpload`+_CompleteUpload :: CompleteUpload+_CompleteUpload = CompleteUpload+ { md5 = Nothing+ }++-- OpenAI says that the `md5` field is optional, but what they really mean is+-- that it can be set to the empty string. We still model it as `Maybe Text`,+-- but convert `Nothing` to an empty string at encoding time.+instance ToJSON CompleteUpload where+ toJSON completeUpload = genericToJSON aesonOptions (fix completeUpload)+ where+ fix CompleteUpload{ md5 = Nothing, .. } =+ CompleteUpload{ md5 = Just "", .. }+ fix x = x++-- | The status of the Upload+data Status+ = Pending+ | Completed+ | Cancelled+ | Expired+ deriving stock (Generic, Show)++instance FromJSON Status where+ parseJSON = genericParseJSON aesonOptions++-- | The Upload object can accept byte chunks in the form of Parts.+data UploadObject file = UploadObject+ { id :: UploadID+ , created_at :: POSIXTime+ , filename :: Text+ , bytes :: Natural+ , purpose :: Purpose+ , status :: Status+ , expires_at :: POSIXTime+ , object :: Text+ , file :: file+ } deriving stock (Generic, Show)++instance FromJSON (UploadObject (Maybe Void))+instance FromJSON (UploadObject FileObject)++-- | The upload part represents a chunk of bytes we can add to an upload+-- object+data PartObject = PartObject+ { id :: Text+ , created_at :: POSIXTime+ , upload_id :: UploadID+ , object :: Text+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API+ = "uploads"+ :> ( ReqBody '[JSON] CreateUpload+ :> Post '[JSON] (UploadObject (Maybe Void))+ :<|> Capture "upload_id" UploadID+ :> "parts"+ :> MultipartForm Tmp AddUploadPart+ :> Post '[JSON] PartObject+ :<|> Capture "upload_id" UploadID+ :> "complete"+ :> ReqBody '[JSON] CompleteUpload+ :> Post '[JSON] (UploadObject FileObject)+ :<|> Capture "upload_id" UploadID+ :> "cancel"+ :> Post '[JSON] (UploadObject (Maybe Void))+ )
+ src/OpenAI/V1/Usage.hs view
@@ -0,0 +1,39 @@+-- | The `Usage` type+module OpenAI.V1.Usage+ ( -- * Main types+ Usage(..)++ -- * Other types+ , CompletionTokensDetails(..)+ , PromptTokensDetails(..)+ ) where++import OpenAI.Prelude++-- | Breakdown of tokens used in a completion+data CompletionTokensDetails = CompletionTokensDetails+ { accepted_prediction_tokens :: Natural+ , audio_tokens :: Natural+ , reasoning_tokens :: Natural+ , rejected_prediction_tokens :: Natural+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Breakdown of tokens used in the prompt+data PromptTokensDetails = PromptTokensDetails+ { audio_tokens :: Natural+ , cached_tokens :: Natural+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Usage statistics for the completion request+data Usage completionTokensDetails promptTokensDetails = Usage+ { completion_tokens :: Natural+ , prompt_tokens :: Natural+ , total_tokens :: Natural+ , completion_tokens_details :: completionTokensDetails+ , prompt_tokens_details :: promptTokensDetails+ } deriving stock (Generic, Show)++instance FromJSON (Usage CompletionTokensDetails PromptTokensDetails)+instance FromJSON (Usage (Maybe Void) (Maybe Void))
+ src/OpenAI/V1/VectorStores.hs view
@@ -0,0 +1,116 @@+-- | @\/v1\/vector_stores@+module OpenAI.V1.VectorStores+ ( -- * Main types+ VectorStoreID(..)+ , CreateVectorStore(..)+ , _CreateVectorStore+ , ModifyVectorStore(..)+ , _ModifyVectorStore+ , VectorStoreObject(..)++ -- * Other types+ , ExpiresAfter(..)+ , Status(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.AutoOr+import OpenAI.V1.ChunkingStrategy+import OpenAI.V1.DeletionStatus+import OpenAI.V1.Files (FileID)+import OpenAI.V1.Order+import OpenAI.V1.ListOf+import OpenAI.V1.VectorStores.FileCounts++-- | Vector store ID+newtype VectorStoreID = VectorStoreID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | The expiration policy for a vector store.+data ExpiresAfter = ExpiresAfter+ { anchor :: Text+ , days :: Natural+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON, ToJSON)++-- | Request body for @\/v1\/vector_stores@+data CreateVectorStore = CreateVectorStore+ { file_ids :: Vector FileID+ , name :: Maybe Text+ , expires_after :: Maybe ExpiresAfter+ , chunking_strategy :: Maybe (AutoOr ChunkingStrategy)+ , metadata :: Maybe (Map Text Text)+ } deriving stock (Generic, Show)+ deriving (ToJSON)++-- | Default `CreateVectorStore`+_CreateVectorStore :: CreateVectorStore+_CreateVectorStore = CreateVectorStore+ { name = Nothing+ , expires_after = Nothing+ , chunking_strategy = Nothing+ , metadata = Nothing+ }++-- | Request body for @\/v1\/vector_stores\/:vector_store_id@+data ModifyVectorStore = ModifyVectorStore+ { name :: Maybe Text+ , expires_after :: Maybe ExpiresAfter+ , metadata :: Maybe (Map Text Text)+ } deriving stock (Generic, Show)+ deriving (ToJSON)++-- | Default `ModifyVectorStore`+_ModifyVectorStore :: ModifyVectorStore+_ModifyVectorStore = ModifyVectorStore+ { name = Nothing+ , expires_after = Nothing+ , metadata = Nothing+ }++-- | The status of the vector store+data Status = Expired | In_Progress | Completed+ deriving stock (Generic, Show)++instance FromJSON Status where+ parseJSON = genericParseJSON aesonOptions++-- | A vector store is a collection of processed files can be used by the+-- @file_search@ tool.+data VectorStoreObject = VectorStoreObject+ { id :: VectorStoreID+ , object :: Text+ , created_at :: POSIXTime+ , name :: Maybe Text+ , usage_bytes :: Natural+ , file_counts :: FileCounts+ , status :: Status+ , expires_after :: Maybe ExpiresAfter+ , expires_at :: Maybe POSIXTime+ , last_active_at :: Maybe POSIXTime+ , metadata :: Map Text Text+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "vector_stores"+ :> Header' '[Required, Strict] "OpenAI-Beta" Text+ :> ( ReqBody '[JSON] CreateVectorStore+ :> Post '[JSON] VectorStoreObject+ :<|> QueryParam "limit" Natural+ :> QueryParam "order" Order+ :> QueryParam "after" Text+ :> QueryParam "before" Text+ :> Get '[JSON] (ListOf VectorStoreObject)+ :<|> Capture "vector_store_id" VectorStoreID+ :> Get '[JSON] VectorStoreObject+ :<|> Capture "vector_store_id" VectorStoreID+ :> ReqBody '[JSON] ModifyVectorStore+ :> Post '[JSON] VectorStoreObject+ :<|> Capture "vector_store_id" VectorStoreID+ :> Delete '[JSON] DeletionStatus+ )
+ src/OpenAI/V1/VectorStores/FileBatches.hs view
@@ -0,0 +1,77 @@+-- | @\/v1\/vector_stores\/:vector_store_id\/file_batches@+module OpenAI.V1.VectorStores.FileBatches+ ( -- * Main types+ VectorStoreFileBatchID(..)+ , CreateVectorStoreFileBatch(..)+ , _CreateVectorStoreFileBatch+ , VectorStoreFilesBatchObject(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.ChunkingStrategy+import OpenAI.V1.Files (FileID)+import OpenAI.V1.ListOf+import OpenAI.V1.Order+import OpenAI.V1.VectorStores (VectorStoreID)+import OpenAI.V1.VectorStores.FileCounts+import OpenAI.V1.VectorStores.Status++-- | Vector store file batch ID+newtype VectorStoreFileBatchID = VectorStoreFileBatchID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | Request body for @\/v1\/vector_stores\/:vector_store_id\/file_batches@+data CreateVectorStoreFileBatch = CreateVectorStoreFileBatch+ { file_ids :: Vector FileID+ , chunking_strategy :: Maybe ChunkingStrategy+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Default `CreateVectorStoreFileBatch`+_CreateVectorStoreFileBatch :: CreateVectorStoreFileBatch+_CreateVectorStoreFileBatch = CreateVectorStoreFileBatch+ { chunking_strategy = Nothing+ }++-- | A batch of files attached to a vector store+data VectorStoreFilesBatchObject = VectorStoreFilesBatchObject+ { id :: VectorStoreFileBatchID+ , object :: Text+ , created_at :: POSIXTime+ , vector_store_id :: VectorStoreID+ , status :: Status+ , file_counts :: Maybe FileCounts+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "vector_stores"+ :> Header' '[Required, Strict] "OpenAI-Beta" Text+ :> ( Capture "vector_store_id" VectorStoreID+ :> "file_batches"+ :> ReqBody '[JSON] CreateVectorStoreFileBatch+ :> Post '[JSON] VectorStoreFilesBatchObject+ :<|> Capture "vector_store_id" VectorStoreID+ :> "file_batches"+ :> Capture "batch_id" VectorStoreFileBatchID+ :> Get '[JSON] VectorStoreFilesBatchObject+ :<|> Capture "vector_store_id" VectorStoreID+ :> "file_batches"+ :> Capture "batch_id" VectorStoreFileBatchID+ :> "cancel"+ :> Post '[JSON] VectorStoreFilesBatchObject+ :<|> Capture "vector_store_id" VectorStoreID+ :> "file_batches"+ :> Capture "batch_id" VectorStoreFileBatchID+ :> "files"+ :> QueryParam "limit" Natural+ :> QueryParam "order" Order+ :> QueryParam "after" Text+ :> QueryParam "before" Text+ :> QueryParam "filter" Status+ :> Get '[JSON] (ListOf VectorStoreFilesBatchObject)+ )
+ src/OpenAI/V1/VectorStores/FileCounts.hs view
@@ -0,0 +1,17 @@+-- | The `FileCounts` type+module OpenAI.V1.VectorStores.FileCounts+ ( -- * Main types+ FileCounts(..)+ ) where++import OpenAI.Prelude++-- | File counts+data FileCounts = FileCounts+ { in_progress :: Natural+ , completed :: Natural+ , failed :: Natural+ , cancelled :: Natural+ , total :: Natural+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)
+ src/OpenAI/V1/VectorStores/Files.hs view
@@ -0,0 +1,77 @@+-- | @\/v1\/vector_stores\/:vector_store_id\/files@+module OpenAI.V1.VectorStores.Files+ ( -- * Main types+ VectorStoreFileID(..)+ , CreateVectorStoreFile(..)+ , _CreateVectorStoreFile+ , VectorStoreFileObject(..)++ -- * Servant+ , API+ ) where++import OpenAI.Prelude+import OpenAI.V1.ChunkingStrategy+import OpenAI.V1.DeletionStatus+import OpenAI.V1.Files (FileID)+import OpenAI.V1.Error+import OpenAI.V1.ListOf+import OpenAI.V1.Order+import OpenAI.V1.VectorStores (VectorStoreID)+import OpenAI.V1.VectorStores.Status++-- | Vector store file ID+newtype VectorStoreFileID = VectorStoreFileID{ text :: Text }+ deriving newtype (FromJSON, IsString, Show, ToHttpApiData, ToJSON)++-- | Request body for @\/v1\/vector_stores\/:vector_store_id\/files@+data CreateVectorStoreFile = CreateVectorStoreFile+ { file_id :: FileID+ , chunking_strategy :: Maybe ChunkingStrategy+ } deriving stock (Generic, Show)+ deriving anyclass (ToJSON)++-- | Default `CreateVectorStoreFile`+_CreateVectorStoreFile :: CreateVectorStoreFile+_CreateVectorStoreFile = CreateVectorStoreFile+ { chunking_strategy = Nothing+ }++-- | A list of files attached to a vector store+data VectorStoreFileObject = VectorStoreFileObject+ { id :: VectorStoreFileID+ , object :: Text+ , usage_bytes :: Natural+ , created_at :: POSIXTime+ , vector_store_id :: VectorStoreID+ , status :: Status+ , last_error :: Maybe Error+ , chunking_strategy :: ChunkingStrategy+ } deriving stock (Generic, Show)+ deriving anyclass (FromJSON)++-- | Servant API+type API =+ "vector_stores"+ :> Header' '[Required, Strict] "OpenAI-Beta" Text+ :> ( Capture "vector_store_id" VectorStoreID+ :> "files"+ :> ReqBody '[JSON] CreateVectorStoreFile+ :> Post '[JSON] VectorStoreFileObject+ :<|> Capture "vector_store_id" VectorStoreID+ :> "files"+ :> QueryParam "limit" Natural+ :> QueryParam "order" Order+ :> QueryParam "after" Text+ :> QueryParam "before" Text+ :> QueryParam "filter" Status+ :> Get '[JSON] (ListOf VectorStoreFileObject)+ :<|> Capture "vector_store_id" VectorStoreID+ :> "files"+ :> Capture "file_id" VectorStoreFileID+ :> Get '[JSON] VectorStoreFileObject+ :<|> Capture "vector_store_id" VectorStoreID+ :> "files"+ :> Capture "file_id" VectorStoreFileID+ :> Delete '[JSON] DeletionStatus+ )
+ src/OpenAI/V1/VectorStores/Status.hs view
@@ -0,0 +1,20 @@+-- | The `Status` type+module OpenAI.V1.VectorStores.Status+ ( -- * Main types+ Status(..)+ ) where++import OpenAI.Prelude++-- | The status of the vector store file+data Status = In_Progress | Completed | Cancelled | Failed+ deriving stock (Generic, Show)++instance FromJSON Status where+ parseJSON = genericParseJSON aesonOptions++instance ToHttpApiData Status where+ toUrlPiece In_Progress = "in_progress"+ toUrlPiece Completed = "completed"+ toUrlPiece Cancelled = "cancelled"+ toUrlPiece Failed = "failed"
+ tasty/Main.hs view
@@ -0,0 +1,653 @@+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}++{-# OPTIONS_GHC -Wno-orphans #-}++module Main where++import OpenAI.V1 (Methods(..))+import OpenAI.V1.AutoOr (AutoOr(..))+import OpenAI.V1.Audio.Transcriptions (CreateTranscription(..))+import OpenAI.V1.Audio.Translations (CreateTranslation(..))+import OpenAI.V1.Batches (BatchObject(..), CreateBatch(..))+import OpenAI.V1.Embeddings (CreateEmbeddings(..), EncodingFormat(..))+import OpenAI.V1.Files (FileObject(..), Order(..), UploadFile(..))+import OpenAI.V1.Images.Edits (CreateImageEdit(..))+import OpenAI.V1.Images.Variations (CreateImageVariation(..))+import OpenAI.V1.Message (Message(..))+import OpenAI.V1.Moderations (CreateModeration(..))+import OpenAI.V1.Threads.Messages (MessageObject(..), ModifyMessage(..))+import OpenAI.V1.Tool (Tool(..), ToolChoice(..))+import OpenAI.V1.ToolCall (ToolCall(..))+import Prelude hiding (id)++import OpenAI.V1.Assistants+ (CreateAssistant(..), ModifyAssistant(..), AssistantObject(..))+import OpenAI.V1.Audio.Speech+ (_CreateSpeech, CreateSpeech(..), Voice(..))+import OpenAI.V1.Chat.Completions+ (CreateChatCompletion(..), Modality(..))+import OpenAI.V1.FineTuning.Jobs+ (CreateFineTuningJob(..), Hyperparameters(..), JobObject(..))+import OpenAI.V1.Images.Generations+ (CreateImage(..), Quality(..), Style(..))+import OpenAI.V1.Threads+ (ModifyThread(..), Thread(..), ThreadObject(..))+import OpenAI.V1.Threads.Runs+ (CreateRun(..), ModifyRun(..), RunObject(..))+import OpenAI.V1.Uploads+ ( AddUploadPart(..)+ , CompleteUpload(..)+ , CreateUpload(..)+ , PartObject(..)+ , UploadObject(..)+ )+import OpenAI.V1.VectorStores+ ( CreateVectorStore(..)+ , ModifyVectorStore(..)+ , VectorStoreObject(..)+ )+import OpenAI.V1.VectorStores.Files+ (CreateVectorStoreFile(..), VectorStoreFileObject(..))+import OpenAI.V1.VectorStores.FileBatches+ (CreateVectorStoreFileBatch(..), VectorStoreFilesBatchObject(..))++import qualified Data.Text as Text+import qualified Network.HTTP.Client as HTTP.Client+import qualified Network.HTTP.Client.TLS as TLS+import qualified OpenAI.V1 as V1+import qualified OpenAI.V1.Chat.Completions as Completions+import qualified OpenAI.V1.Files as Files+import qualified OpenAI.V1.FineTuning.Jobs as Jobs+import qualified OpenAI.V1.Images.ResponseFormat as ResponseFormat+import qualified OpenAI.V1.Tool as Tool+import qualified OpenAI.V1.ToolCall as ToolCall+import qualified Servant.Client as Client+import qualified System.Environment as Environment+import qualified Test.Tasty as Tasty+import qualified Test.Tasty.HUnit as HUnit++main :: IO ()+main = do+ let managerSettings = TLS.tlsManagerSettings+ { HTTP.Client.managerResponseTimeout =+ HTTP.Client.responseTimeoutNone+ }++ manager <- TLS.newTlsManagerWith managerSettings++ baseUrl <- Client.parseBaseUrl "https://api.openai.com"++ let clientEnv = Client.mkClientEnv manager baseUrl++ key <- Environment.getEnv "OPENAI_KEY"++ let user = "openai Haskell package"+ let chatModel = "gpt-4o-mini"++ let Methods{..} = V1.makeMethods clientEnv (Text.pack key)++ -- Test each format to make sure we're handling each possible content type+ -- correctly+ let speechTest format =+ HUnit.testCase ("Create speech - " <> show format) do+ _ <- createSpeech _CreateSpeech+ { model = "tts-1"+ , input = "Hello, world!"+ , voice = Nova+ , response_format = Just format+ , speed = Just 1.0+ }++ return ()++ let speechTests = do+ format <- [ minBound .. maxBound ]+ return (speechTest format)++ let transcriptionTest =+ HUnit.testCase "Create transcription" do+ _ <- createTranscription CreateTranscription+ { file = "tasty/data/v1/audio/preamble.wav"+ , model = "whisper-1"+ , language = Just "en"+ , prompt = Nothing+ , temperature = Just 0+ }++ return ()++ let translationTest =+ HUnit.testCase "Create translation" do+ _ <- createTranslation CreateTranslation+ { file = "tasty/data/v1/audio/preamble.wav"+ , model = "whisper-1"+ , prompt = Nothing+ , temperature = Just 0+ }++ return ()++ let completionsMinimalTest =+ HUnit.testCase "Create chat completion - minimal" do+ _ <- createChatCompletion CreateChatCompletion+ { messages =+ [ Completions.User+ { content = [ "Hello, world!" ], name = Nothing }+ ]+ , model = chatModel+ , store = Nothing+ , metadata = Nothing+ , frequency_penalty = Nothing+ , logit_bias = Nothing+ , logprobs = Nothing+ , top_logprobs = Nothing+ , max_completion_tokens = Nothing+ , n = Nothing+ , modalities = Nothing+ , prediction = Nothing+ , audio = Nothing+ , presence_penalty = Nothing+ , response_format = Nothing+ , seed = Nothing+ , service_tier = Nothing+ , stop = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , tools = Nothing+ , tool_choice = Nothing+ , parallel_tool_calls = Nothing+ , user = Nothing+ }++ return ()++ let completionsMaximalTest =+ HUnit.testCase "Create chat completion - maximal" do+ _ <- createChatCompletion CreateChatCompletion+ { messages =+ [ Completions.User+ { content = [ "Hello, world!" ]+ , name = Just "gabby"+ }+ , Completions.Assistant+ { assistant_content = Nothing+ , refusal = Nothing+ , name = Just "Ada"+ , assistant_audio = Nothing+ , tool_calls = Just+ [ ToolCall_Function+ { id = "call_bzE95mjMMFqeanfY2sL6Sdir"+ , function = ToolCall.Function+ { name = "hello"+ , arguments = "{}"+ }+ }+ ]+ }+ , Completions.Tool+ { content = [ "Hello, world!" ]+ , tool_call_id = "call_bzE95mjMMFqeanfY2sL6Sdir"+ }+ ]+ , model = chatModel+ , store = Just False+ , metadata = Nothing+ , frequency_penalty = Just 0+ , logit_bias = Just mempty+ , logprobs = Just True+ , top_logprobs = Just 1+ , max_completion_tokens = Just 1024+ , n = Just 1+ , modalities = Just [ Modality_Text ]+ , prediction = Nothing+ , audio = Nothing+ , presence_penalty = Just 0+ , response_format = Just Completions.ResponseFormat_Text+ , seed = Just 0+ , service_tier = Just Auto+ , stop = Just [ ">>>" ]+ , temperature = Just 1+ , top_p = Just 1+ , tools = Just+ [ Tool_Function+ { function = Tool.Function+ { description =+ Just "Use the hello command line tool"+ , name = "hello"+ , parameters = Nothing+ , strict = Just False+ }+ }+ ]+ , tool_choice = Just ToolChoiceAuto+ , parallel_tool_calls = Just True+ , user = Just user+ }++ return ()++ let embeddingsTest = do+ HUnit.testCase "Create embedding" do+ _ <- createEmbeddings CreateEmbeddings+ { input = "Hello, world!"+ , model = "text-embedding-3-small"+ , encoding_format = Just Float+ , dimensions = Just 1024+ , user = Just user+ }++ return ()++ let fineTuningTest = do+ HUnit.testCase "Fine-tuning and File operations - maximal" do+ FileObject{ id = trainingId } <- uploadFile UploadFile+ { file =+ "tasty/data/v1/fine_tuning/jobs/training_data.jsonl"+ , purpose = Files.Fine_Tune+ }++ FileObject{ id = validationId } <- uploadFile UploadFile+ { file =+ "tasty/data/v1/fine_tuning/jobs/validation_data.jsonl"+ , purpose = Files.Fine_Tune+ }++ _ <- retrieveFile trainingId++ _ <- retrieveFileContent trainingId++ _ <- listFiles (Just Files.Fine_Tune) (Just 10000) (Just Asc) Nothing++ JobObject{ id } <- createFineTuningJob CreateFineTuningJob+ { model = "gpt-4o-mini-2024-07-18"+ , training_file = trainingId+ , hyperparameters = Just+ Hyperparameters+ { batch_size = Just Jobs.Auto+ , learning_rate_multiplier = Just Jobs.Auto+ , n_epochs = Just Jobs.Auto+ }+ , suffix = Just "haskell-openai"+ , validation_file = Just validationId+ , integrations = Just []+ , seed = Just 0+ }++ _ <- retrieveFineTuningJob id++ _ <- listFineTuningJobs Nothing (Just 20)++ _ <- listFineTuningCheckpoints id Nothing (Just 10)++ _ <- cancelFineTuning id++ _ <- listFineTuningEvents id Nothing (Just 20)++ _ <- deleteFile trainingId+ _ <- deleteFile validationId++ return ()++ let batchesTest = do+ HUnit.testCase "Batch operations" do+ FileObject{ id = requestsId } <- uploadFile UploadFile+ { file = "tasty/data/v1/batches/requests.jsonl"+ , purpose = Files.Batch+ }++ BatchObject{ id } <- createBatch CreateBatch+ { input_file_id = requestsId+ , endpoint = "/v1/chat/completions"+ , completion_window = "24h"+ , metadata = Nothing+ }++ _ <- retrieveBatch id++ _ <- listBatch Nothing (Just 20)++ _ <- cancelBatch id++ return ()++ let uploadsTest = do+ HUnit.testCase "Upload operations" do+ UploadObject{ id = cancelledId } <- createUpload CreateUpload+ { filename = "training_data.jsonl"+ , purpose = Files.Fine_Tune+ , bytes = 4077+ , mime_type = "text/jsonl"+ }++ _ <- cancelUpload cancelledId++ UploadObject{ id } <- createUpload CreateUpload+ { filename = "training_data.jsonl"+ , purpose = Files.Fine_Tune+ , bytes = 4077+ , mime_type = "text/jsonl"+ }++ PartObject{ id = partId0 } <- addUploadPart id AddUploadPart+ { data_ = "tasty/data/v1/uploads/training_data0.jsonl" }++ PartObject{ id = partId1 } <- addUploadPart id AddUploadPart+ { data_ = "tasty/data/v1/uploads/training_data1.jsonl" }++ _ <- completeUpload id CompleteUpload+ { part_ids = [ partId0, partId1 ]+ , md5 = Nothing+ }++ return ()++ let createImageMinimalTest = do+ HUnit.testCase "Create image - minimal" do+ _ <- createImage CreateImage+ { prompt = "A baby panda"+ , model = Nothing+ , n = Nothing+ , quality = Nothing+ , response_format = Nothing+ , size = Nothing+ , style = Nothing+ , user = Nothing+ }++ return ()++ let createImageMaximalTest = do+ HUnit.testCase "Create image - maximal" do+ _ <- createImage CreateImage+ { prompt = "A baby panda"+ , model = Just "dall-e-2"+ , n = Just 1+ , quality = Just Standard+ , response_format = Just ResponseFormat.URL+ , size = Just "1024x1024"+ , style = Just Vivid+ , user = Just user+ }++ return ()++ let createImageEditMinimalTest = do+ HUnit.testCase "Create image edit - minimal" do+ _ <- createImageEdit CreateImageEdit+ { image = "tasty/data/v1/images/image.png"+ , prompt = "The panda should be greener"+ , mask = Nothing+ , model = Nothing+ , n = Nothing+ , size = Nothing+ , response_format = Nothing+ , user = Nothing+ }++ return ()++ let createImageEditMaximalTest = do+ HUnit.testCase "Create image edit - maximal" do+ _ <- createImageEdit CreateImageEdit+ { image = "tasty/data/v1/images/image.png"+ , prompt = "The panda should be greener"+ , mask = Nothing+ , model = Just "dall-e-2"+ , n = Just 1+ , size = Just "1024x1024"+ , response_format = Just ResponseFormat.URL+ , user = Just user+ }++ return ()++ let createImageVariationMinimalTest = do+ HUnit.testCase "Create image variation - minimal" do+ _ <- createImageVariation CreateImageVariation+ { image = "tasty/data/v1/images/image.png"+ , model = Nothing+ , n = Nothing+ , response_format = Nothing+ , size = Nothing+ , user = Nothing+ }++ return ()++ let createImageVariationMaximalTest = do+ HUnit.testCase "Create image variation - maximal" do+ _ <- createImageVariation CreateImageVariation+ { image = "tasty/data/v1/images/image.png"+ , model = Just "dall-e-2"+ , n = Just 1+ , response_format = Just ResponseFormat.URL+ , size = Just "1024x1024"+ , user = Just user+ }++ return ()++ let createModerationTest = do+ HUnit.testCase "Create moderation" do+ _ <- createModeration CreateModeration+ { input = "I am going to kill you"+ , model = Nothing+ }++ return ()++ let assistantsTest = do+ HUnit.testCase "Assistant operations" do+ AssistantObject{ id } <- createAssistant CreateAssistant+ { model = chatModel+ , name = Nothing+ , description = Nothing+ , instructions = Nothing+ , tools = Nothing+ , tool_resources = Nothing+ , metadata = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , response_format = Nothing+ }++ _ <- listAssistants Nothing Nothing Nothing Nothing++ _ <- retrieveAssistant id++ _ <- modifyAssistant id ModifyAssistant+ { model = chatModel+ , name = Nothing+ , description = Nothing+ , instructions = Nothing+ , tools = Nothing+ , tool_resources = Nothing+ , metadata = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , response_format = Nothing+ }++ _ <- deleteAssistant id++ return ()++ let messagesTest = do+ HUnit.testCase "Message operations" do+ ThreadObject{ id = threadId } <- createThread Thread+ { messages = Just+ [ User+ { content = [ "Hi, how can I help you!" ]+ , attachments = Nothing+ , metadata = Nothing+ }+ ]+ , tool_resources = Nothing+ , metadata = Nothing+ }++ MessageObject{ id = messageId } <- createMessage threadId User+ { content = [ "What is the capital of France?" ]+ , attachments = Nothing+ , metadata = Nothing+ }++ _ <- retrieveMessage threadId messageId++ _ <- modifyMessage threadId messageId ModifyMessage+ { metadata = Nothing+ }++ _ <- deleteMessage threadId messageId++ _ <- deleteThread threadId++ return ()++ let threadsRunsStepsTest = do+ HUnit.testCase "Thread/Run/Step operations" do+ ThreadObject{ id = threadId } <- createThread Thread+ { messages = Just+ [ User+ { content = [ "Hello, world!" ]+ , attachments = Nothing+ , metadata = Nothing+ }+ ]+ , tool_resources = Nothing+ , metadata = Nothing+ }++ _ <- retrieveThread threadId++ _ <- modifyThread threadId ModifyThread+ { tool_resources = Nothing+ , metadata = Nothing+ }++ AssistantObject{ id = assistantId } <- createAssistant CreateAssistant+ { model = chatModel+ , name = Nothing+ , description = Nothing+ , instructions = Nothing+ , tools = Nothing+ , tool_resources = Nothing+ , metadata = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , response_format = Nothing+ }++ RunObject{ id = runId } <- createRun threadId Nothing CreateRun+ { assistant_id = assistantId+ , model = Nothing+ , instructions = Nothing+ , additional_instructions = Nothing+ , additional_messages = Nothing+ , tools = Nothing+ , metadata = Nothing+ , temperature = Nothing+ , top_p = Nothing+ , max_prompt_tokens = Nothing+ , max_completion_tokens = Nothing+ , truncation_strategy = Nothing+ , tool_choice = Nothing+ , parallel_tool_calls = Nothing+ , response_format = Nothing+ }++ _ <- listRuns threadId Nothing Nothing Nothing Nothing++ _ <- retrieveRun threadId runId++ _ <- modifyRun threadId runId ModifyRun+ { metadata = Nothing+ }++ _ <- deleteThread threadId++ return ()++ let vectorStoreFilesTest = do+ HUnit.testCase "Vector store file and batch operations" do+ FileObject{ id = fileId } <- uploadFile UploadFile+ { file = "tasty/data/v1/vector_stores/index.html"+ , purpose = Files.Assistants+ }++ VectorStoreObject{ id = vectorStoreId } <- createVectorStore CreateVectorStore+ { file_ids = []+ , name = Nothing+ , expires_after = Nothing+ , chunking_strategy = Nothing+ , metadata = Nothing+ }++ VectorStoreFileObject{ id = vectorStoreFileId } <- createVectorStoreFile vectorStoreId CreateVectorStoreFile+ { file_id = fileId+ , chunking_strategy = Nothing+ }++ VectorStoreFilesBatchObject{ id = batchId } <- createVectorStoreFileBatch vectorStoreId CreateVectorStoreFileBatch+ { file_ids = [ fileId ]+ , chunking_strategy = Nothing+ }++ _ <- listVectorStores Nothing Nothing Nothing Nothing++ _ <- listVectorStoreFiles vectorStoreId Nothing Nothing Nothing Nothing Nothing++ _ <- listVectorStoreFilesInABatch vectorStoreId batchId Nothing Nothing Nothing Nothing Nothing++ _ <- retrieveVectorStore vectorStoreId++ _ <- retrieveVectorStoreFile vectorStoreId vectorStoreFileId++ _ <- retrieveVectorStoreFileBatch vectorStoreId batchId++ _ <- modifyVectorStore vectorStoreId ModifyVectorStore+ { name = Nothing+ , expires_after = Nothing+ , metadata = Nothing+ }++ _ <- cancelVectorStoreFileBatch vectorStoreId batchId++ _ <- deleteVectorStoreFile vectorStoreId vectorStoreFileId++ _ <- deleteVectorStore vectorStoreId++ _ <- deleteFile fileId++ return ()++ let tests =+ speechTests+ <> [ transcriptionTest+ , translationTest+ , completionsMinimalTest+ , completionsMaximalTest+ , embeddingsTest+ , fineTuningTest+ , batchesTest+ , uploadsTest+ , createImageMinimalTest+ , createImageMaximalTest+ , createImageEditMinimalTest+ , createImageEditMaximalTest+ , createImageVariationMinimalTest+ , createImageVariationMaximalTest+ , createModerationTest+ , assistantsTest+ , messagesTest+ , threadsRunsStepsTest+ , vectorStoreFilesTest+ ]++ Tasty.defaultMain (Tasty.testGroup "Tests" tests)