ihp-openai: Call GPT4 from your Haskell apps

[ ai, library, mit ] [ Propose Tags ] [ Report a vulnerability ]

Streaming functions to access the OpenAI APIs, with Retry and Function Calling


[Skip to Readme]

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 1.3.0
Change log changelog.md
Dependencies aeson, base (>=4.17.0 && <4.20), bytestring, HsOpenSSL, http-streams, io-streams, retry, text [details]
License MIT
Author digitally induced GmbH
Maintainer support@digitallyinduced.com
Category AI
Bug tracker https://github.com/digitallyinduced/ihp/issues
Source repo head: git clone https://github.com/digitallyinduced/ihp.git
Uploaded by MarcScholten at 2024-07-21T21:48:07Z
Distributions
Downloads 32 total (4 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2024-07-21 [all 1 reports]

Readme for ihp-openai-1.3.0

[back to package description]

OpenAI

This ihp-openai package provides streaming functions to access GPT3 and GPT4 from OpenAI.

The package is designed to work well with IHP AutoRefresh and IHP DataSync. The main function streamCompletion uses the OpenAI Chat API. It was created before the Chat API was available, and the later rewritten to work well with GPT-4. That's why it feels a bit like a mix of the completion and the chat api.

API calls are retried up to 10 times. A retry will continue with the already generated output tokens, so that a user will never see that a retry has happend.

Install

  1. Make sure you're running on the latest master version of IHP

  2. Open default.nix and add ihp-openai to your haskell dependencies:

    let
        ihp = ...;
        haskellEnv = import "${ihp}/NixSupport/default.nix" {
            ihp = ihp;
            haskellDeps = p: with p; [
                cabal-install
                base
                wai
                text
                hlint
                p.ihp
    
                ihp-openai
            ];
            otherDeps = p: with p; [
                # Native dependencies, e.g. imagemagick
            ];
            projectPath = ./.;
        };
    in
        haskellEnv
    
    

Example

module Web.Controller.Questions where

import Web.Controller.Prelude
import Web.View.Questions.Index
import Web.View.Questions.New
import Web.View.Questions.Edit
import Web.View.Questions.Show

import qualified IHP.OpenAI as GPT

instance Controller QuestionsController where
    action QuestionsAction = autoRefresh do
        questions <- query @Question
            |> orderByDesc #createdAt
            |> fetch
        render IndexView { .. }

    action NewQuestionAction = do
        let question = newRecord
                |> set #question "What makes haskell so great?"
        render NewView { .. }

    action CreateQuestionAction = do
        let question = newRecord @Question
        question
            |> fill @'["question"]
            |> validateField #question nonEmpty
            |> ifValid \case
                Left question -> render NewView { .. } 
                Right question -> do
                    question <- question |> createRecord
                    setSuccessMessage "Question created"

                    fillAnswer question

                    redirectTo QuestionsAction

    action DeleteQuestionAction { questionId } = do
        question <- fetch questionId
        deleteRecord question
        setSuccessMessage "Question deleted"
        redirectTo QuestionsAction

fillAnswer :: (?modelContext :: ModelContext) => Question -> IO (Async ())
fillAnswer question = do
    -- Put your OpenAI secret key below:
    let secretKey = "sk-XXXXXXXX"

    -- This should be done with an IHP job worker instead of async
    async do 
        GPT.streamCompletion secretKey (buildCompletionRequest question) (clearAnswer question) (appendToken question)
        pure ()

buildCompletionRequest :: Question -> GPT.CompletionRequest
buildCompletionRequest Question { question } =
    -- Here you can adjust the parameters of the request
    GPT.newCompletionRequest
        { GPT.maxTokens = 512
        , GPT.prompt = [trimming|
                Question: ${question}
                Answer:
        |] }

-- | Sets the answer field back to an empty string
clearAnswer :: (?modelContext :: ModelContext) => Question -> IO ()
clearAnswer question = do
    sqlExec "UPDATE questions SET answer = '' WHERE id = ?" (Only question.id)
    pure ()

-- | Stores a couple of newly received characters to the database
appendToken :: (?modelContext :: ModelContext) => Question -> Text -> IO ()
appendToken question token = do
    sqlExec "UPDATE questions SET answer = answer || ? WHERE id = ?" (token, question.id)
    pure ()