Got asking GPT questions working.

This commit is contained in:
Martin Sosic 2023-04-19 21:26:44 +02:00
parent 71acb26bbc
commit 33f8257549
2 changed files with 99 additions and 5 deletions

View File

@ -1,14 +1,24 @@
{-# LANGUAGE DeriveGeneric #-}
module Wasp.Cli.Command.AI.New module Wasp.Cli.Command.AI.New
( new, ( new,
queryChatGpt,
sayHiToChatGpt,
) )
where where
import Control.Arrow () import Control.Arrow ()
import Control.Monad.Except (MonadIO (liftIO)) import Control.Monad.Except (MonadIO (liftIO))
import Data.Aeson ((.=))
import qualified Data.Aeson as Aeson
import Data.ByteString.UTF8 as BSU
import Data.Text (Text) import Data.Text (Text)
import GHC.Generics (Generic)
import qualified Network.HTTP.Simple as HTTP
import StrongPath (Abs, Dir, Path', fromAbsDir, fromRelFile, relfile, (</>)) import StrongPath (Abs, Dir, Path', fromAbsDir, fromRelFile, relfile, (</>))
import StrongPath.Operations () import StrongPath.Operations ()
import System.Directory (getFileSize, setCurrentDirectory) import System.Directory (getFileSize, setCurrentDirectory)
import qualified System.Environment as System.Environment
import qualified Wasp.AppSpec.Valid as ASV import qualified Wasp.AppSpec.Valid as ASV
import Wasp.Cli.Command (Command) import Wasp.Cli.Command (Command)
import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd, readWaspCompileInfo) import Wasp.Cli.Command.Common (findWaspProjectRootDirFromCwd, readWaspCompileInfo)
@ -77,8 +87,91 @@ new = do
aiWriteWaspOperations absWaspProjectDir waspFileContent = do aiWriteWaspOperations absWaspProjectDir waspFileContent = do
error "TODO" error "TODO"
sendPromptToGpt :: String -> IO () -- In general, gpt-3.5-turbo-0301 does not pay strong attention to the system message, and therefore important instructions are often better placed in a user message.
sendPromptToGpt prompt = do
-- TODO: https://platform.openai.com/docs/guides/chat/introduction sayHiToChatGpt :: IO ()
-- TODO: https://www.reddit.com/r/haskell/comments/1263sfl/haskellai_stable_package_set_shell_and/ -> also mentions openai-hs and a fork of it which is more up to date it seems. sayHiToChatGpt = do
undefined apiKey <- System.Environment.getEnv "OPENAI_API_KEY"
answer <- queryChatGpt apiKey [ChatMessage {role = User, content = "What is 2 + 2?"}]
putStrLn answer
-- TODO: We will need to have this on a server somewhere, not here, if we want to use our API keys.
-- If we let them use their API keys then it can be here.
queryChatGpt :: String -> [ChatMessage] -> IO String
queryChatGpt apiKey requestMessages = do
-- TODO: We could try playing with parameters like temperature, max_tokens, ... .
let reqBodyJson =
Aeson.object
[ "model" .= ("gpt-3.5-turbo" :: String),
"messages" .= requestMessages
]
request =
HTTP.setRequestHeader "Authorization" [BSU.fromString $ "Bearer " <> apiKey] $
HTTP.setRequestBodyJSON reqBodyJson $
HTTP.parseRequest_ "POST https://api.openai.com/v1/chat/completions"
-- TODO: Consider using httpJSONEither here, so I can handle errors better.
response <- HTTP.httpJSON request
-- TODO: I should probably check status code here, confirm it is 200.
let responseStatusCode = HTTP.getResponseStatusCode response
(chatResponse :: ChatResponse) = HTTP.getResponseBody response
putStrLn $ "Response status code:" <> show responseStatusCode
putStrLn $ "Response body:" <> show chatResponse
return $ content $ message $ head $ choices chatResponse
data ChatResponse = ChatResponse
{ id :: !String,
object :: !String,
created :: !Int,
model :: !String,
choices :: ![ChatResponseChoice],
usage :: !ChatResponseUsage
}
deriving (Generic, Show)
instance Aeson.FromJSON ChatResponse
data ChatResponseUsage = ChatResponseUsage
{ prompt_tokens :: !Int,
completion_tokens :: !Int,
total_tokens :: !Int
}
deriving (Generic, Show)
instance Aeson.FromJSON ChatResponseUsage
data ChatResponseChoice = ChatResponseChoice
{ index :: !Int,
message :: !ChatMessage,
finish_reason :: !String
}
deriving (Generic, Show)
instance Aeson.FromJSON ChatResponseChoice
data ChatMessage = ChatMessage
{ role :: !ChatRole,
content :: !String
}
deriving (Generic, Show)
instance Aeson.ToJSON ChatMessage
instance Aeson.FromJSON ChatMessage
data ChatRole = User | System | Assistant
deriving (Generic, Show)
instance Aeson.ToJSON ChatRole where
toJSON User = "user"
toJSON System = "system"
toJSON Assistant = "assistant"
instance Aeson.FromJSON ChatRole where
parseJSON = Aeson.withText "ChatRole" $ \case
"user" -> return User
"system" -> return System
"assistant" -> return Assistant
other -> fail $ "Invalid ChatRole: " <> show other

View File

@ -365,6 +365,7 @@ library cli-lib
exposed-modules: exposed-modules:
Wasp.Cli.Command Wasp.Cli.Command
Wasp.Cli.FileSystem Wasp.Cli.FileSystem
Wasp.Cli.Command.AI.New
Wasp.Cli.Command.BashCompletion Wasp.Cli.Command.BashCompletion
Wasp.Cli.Command.Build Wasp.Cli.Command.Build
Wasp.Cli.Command.Call Wasp.Cli.Command.Call