Skip to content

Commit 53fb770

Browse files
committed
Adds an OpenAI Behavior
1 parent 3787251 commit 53fb770

File tree

5 files changed

+86
-17
lines changed

5 files changed

+86
-17
lines changed

app/Main.hs

+18-13
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import Control.Monad.Except
1313
import Control.Monad.IO.Class (liftIO)
1414
import Data.Foldable
1515
import GHC.Conc (threadDelay)
16+
import Network.HTTP.Client qualified as HTTP
17+
import Network.HTTP.Client.TLS qualified as HTTP.TLS
1618
import Network.Matrix.Client
19+
import OpenAI.Client (makeOpenAIClient)
1720
import Options.Applicative qualified as Opt
1821
import OptionsParser
1922
import System.Environment.XDG.BaseDir (getUserCacheDir)
@@ -23,17 +26,17 @@ main :: IO ()
2326
main = do
2427
command <- Opt.execParser parserInfo
2528
xdgCache <- getUserCacheDir "cofree-bot"
26-
29+
httpManager <- HTTP.newManager HTTP.TLS.tlsManagerSettings
2730
case command of
28-
LoginCmd cred -> do
31+
LoginCmd cred openAIKey -> do
2932
session <- login cred
30-
matrixMain session xdgCache
31-
TokenCmd TokenCredentials {..} -> do
33+
matrixMain session xdgCache httpManager openAIKey
34+
TokenCmd TokenCredentials {..} openAIKey -> do
3235
session <- createSession (getMatrixServer matrixServer) matrixToken
33-
matrixMain session xdgCache
34-
CLI -> cliMain xdgCache
36+
matrixMain session xdgCache httpManager openAIKey
37+
CLI openAIKey -> cliMain xdgCache httpManager openAIKey
3538

36-
bot process =
39+
bot process manager (OpenAIKey aiKey) =
3740
let calcBot =
3841
embedTextBot $
3942
simplifySessionBot printCalcOutput statementP $
@@ -43,29 +46,31 @@ bot process =
4346
coinFlipBot' = embedTextBot $ simplifyCoinFlipBot coinFlipBot
4447
ghciBot' = embedTextBot $ ghciBot process
4548
magic8BallBot' = embedTextBot $ simplifyMagic8BallBot magic8BallBot
49+
openAIBot' = openAIBot $ makeOpenAIClient aiKey manager 2
4650
in calcBot
4751
/.\ coinFlipBot'
4852
/.\ helloBot
4953
/.\ ghciBot'
5054
/.\ magic8BallBot'
5155
/.\ updogMatrixBot
5256
/.\ embedTextBot jitsiBot
57+
/.\ embedTextBot openAIBot'
5358

54-
cliMain :: FilePath -> IO ()
55-
cliMain xdgCache = withProcessWait_ ghciConfig $ \process -> do
59+
cliMain :: FilePath -> HTTP.Manager -> OpenAIKey -> IO ()
60+
cliMain xdgCache manager openAIKey = withProcessWait_ ghciConfig $ \process -> do
5661
void $ threadDelay 1e6
5762
void $ hGetOutput (getStdout process)
5863
state <- readState xdgCache
59-
fixedBot <- flip (fixBotPersistent xdgCache) (fold state) $ simplifyMatrixBot $ bot process
64+
fixedBot <- flip (fixBotPersistent xdgCache) (fold state) $ simplifyMatrixBot $ bot process manager openAIKey
6065
void $ loop $ annihilate repl fixedBot
6166

6267
unsafeCrashInIO :: Show e => ExceptT e IO a -> IO a
6368
unsafeCrashInIO = runExceptT >=> either (fail . show) pure
6469

65-
matrixMain :: ClientSession -> FilePath -> IO ()
66-
matrixMain session xdgCache = withProcessWait_ ghciConfig $ \process -> do
70+
matrixMain :: ClientSession -> FilePath -> HTTP.Manager -> OpenAIKey -> IO ()
71+
matrixMain session xdgCache manager openAIKey = withProcessWait_ ghciConfig $ \process -> do
6772
void $ threadDelay 1e6
6873
void $ hGetOutput (getStdout process)
6974
state <- readState xdgCache
70-
fixedBot <- flip (fixBotPersistent xdgCache) (fold state) $ hoistBot liftIO $ bot process
75+
fixedBot <- flip (fixBotPersistent xdgCache) (fold state) $ hoistBot liftIO $ bot process manager openAIKey
7176
unsafeCrashInIO $ loop $ annihilate (matrix session xdgCache) $ batch fixedBot

app/OptionsParser.hs

+21-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module OptionsParser where
22

3+
import Control.Applicative
34
import Data.Text qualified as T
45
import Network.Matrix.Client
56
import Options.Applicative qualified as Opt
@@ -93,30 +94,46 @@ parseServer =
9394
"Matrix Homeserver"
9495
)
9596

97+
-----------------------
98+
--- Behavior Config ---
99+
-----------------------
100+
101+
newtype OpenAIKey = OpenAIKey T.Text
102+
103+
parseOpenAIKey :: Opt.Parser OpenAIKey
104+
parseOpenAIKey =
105+
OpenAIKey
106+
<$> Opt.strOption
107+
( Opt.long "openai_key"
108+
<> Opt.metavar "OPENAI_KEY"
109+
<> Opt.help
110+
"OpenAI API Key"
111+
)
112+
96113
-------------------
97114
--- Main Parser ---
98115
-------------------
99116

100-
data Command = LoginCmd LoginCredentials | TokenCmd TokenCredentials | CLI
117+
data Command = LoginCmd LoginCredentials OpenAIKey | TokenCmd TokenCredentials OpenAIKey | CLI OpenAIKey
101118

102119
mainParser :: Opt.Parser Command
103120
mainParser =
104121
Opt.subparser
105122
( Opt.command
106123
"gen-token"
107124
( Opt.info
108-
(fmap LoginCmd parseLogin)
125+
(liftA2 LoginCmd parseLogin parseOpenAIKey)
109126
(Opt.progDesc "Generate a token from a username/password")
110127
)
111128
<> Opt.command
112129
"run"
113130
( Opt.info
114-
(fmap TokenCmd parseTokenCredentials)
131+
(liftA2 TokenCmd parseTokenCredentials parseOpenAIKey)
115132
(Opt.progDesc "Run the bot with an auth token")
116133
)
117134
<> Opt.command
118135
"cli"
119-
(Opt.info (pure CLI) (Opt.progDesc "Run the bot in the CLI"))
136+
(Opt.info (fmap CLI parseOpenAIKey) (Opt.progDesc "Run the bot in the CLI"))
120137
)
121138

122139
parserInfo :: Opt.ParserInfo Command

cofree-bot.cabal

+5
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ executable cofree-bot
6767
hs-source-dirs: app
6868
build-depends:
6969
, cofree-bot
70+
, http-client
71+
, http-client-tls
7072
, mtl
73+
, openai-hs
7174
, optparse-applicative
7275
, xdg-basedir
7376

@@ -93,6 +96,7 @@ library
9396
CofreeBot.Bot.Behaviors.Jitsi
9497
CofreeBot.Bot.Behaviors.Jitsi.Dictionary
9598
CofreeBot.Bot.Behaviors.Magic8Ball
99+
CofreeBot.Bot.Behaviors.OpenAI
96100
CofreeBot.Bot.Behaviors.Updog
97101
CofreeBot.Bot.Context
98102
CofreeBot.Utils
@@ -110,6 +114,7 @@ library
110114
, lens
111115
, monad-loops
112116
, mtl
117+
, openai-hs
113118
, pretty-simple
114119
, process
115120
, random

src/CofreeBot/Bot/Behaviors.hs

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module CofreeBot.Bot.Behaviors
55
module Hello,
66
module Jitsi,
77
module Magic8Ball,
8+
module OpenAI,
89
module Updog,
910
)
1011
where
@@ -15,4 +16,5 @@ import CofreeBot.Bot.Behaviors.GHCI as GHCI
1516
import CofreeBot.Bot.Behaviors.Hello as Hello
1617
import CofreeBot.Bot.Behaviors.Jitsi as Jitsi
1718
import CofreeBot.Bot.Behaviors.Magic8Ball as Magic8Ball
19+
import CofreeBot.Bot.Behaviors.OpenAI as OpenAI
1820
import CofreeBot.Bot.Behaviors.Updog as Updog

src/CofreeBot/Bot/Behaviors/OpenAI.hs

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{-# LANGUAGE ViewPatterns #-}
2+
3+
-- | A bot for general interactions with OpenAI's GPT LLM.
4+
module CofreeBot.Bot.Behaviors.OpenAI
5+
( openAIBot,
6+
)
7+
where
8+
9+
--------------------------------------------------------------------------------
10+
11+
import CofreeBot.Bot
12+
import CofreeBot.Utils.ListT (emptyListT)
13+
import Control.Monad.IO.Class (MonadIO (..))
14+
import Data.Attoparsec.Text
15+
import Data.Text (Text)
16+
import Data.Text qualified as T
17+
import Data.Vector qualified as V
18+
import OpenAI.Client qualified as OpenAI
19+
20+
--------------------------------------------------------------------------------
21+
22+
openAIBot :: OpenAI.OpenAIClient -> Bot IO () Text Text
23+
openAIBot client =
24+
contraMapMaybeBot (either (const Nothing) Just . parseOnly openAIBotParser) $
25+
Bot $ \s (buildPrompt -> i) -> do
26+
liftIO (OpenAI.completeText client (OpenAI.EngineId "text-davinci-003") (i {OpenAI.tccrMaxTokens = Just 2096})) >>= \case
27+
Left _err -> emptyListT
28+
Right OpenAI.TextCompletion {tcChoices} ->
29+
let OpenAI.TextCompletionChoice {..} = V.head tcChoices
30+
in pure (T.strip tccText, s)
31+
32+
buildPrompt :: Text -> OpenAI.TextCompletionCreate
33+
buildPrompt input =
34+
let preamble = "You are a friendly chat bot named Cofree-Bot on a server dedicated to functional programming. Please respond to the following prompt:"
35+
in OpenAI.defaultTextCompletionCreate $ preamble <> input
36+
37+
openAIBotParser :: Parser Text
38+
openAIBotParser = do
39+
_ <- "chat: "
40+
T.pack <$> many1 anyChar

0 commit comments

Comments
 (0)