packages feed

genai-lib-1.3: src/app/GenAI/Opts.hs

{-# LANGUAGE DuplicateRecordFields, OverloadedRecordDot, QuasiQuotes #-}

module GenAI.Opts
  ( parseOpts
  )
  where

import Data.Text.Lazy (pack)
import Data.Version (showVersion)
import Formatting ((%+), format, formatToString, string)
import Formatting.ShortFormatters (s)
import GenAILib.Common (Model (..), RawOutput (..), convertOptions)
import GenAILib.Ollama (Host (..), Stream (..),
  System (..), defaultHost, hostFromString)
import Options.Applicative
import Paths_genai_lib (version)
import Prettyprinter (pretty)
import System.Environment (getProgName)
import Text.Heredoc (here)

import GenAI.Common (CLIOptions (..), Verbose (..))


readHost :: ReadM Host
readHost = eitherReader (\hostPortStr ->
  maybe
    (Left $ formatToString
      ("Unable to parse" %+ string %+ "into host and port parts") hostPortStr)
    Right
    $ hostFromString hostPortStr
  )


{- HLINT ignore "Functor law" -}
parser :: Parser CLIOptions
parser = CLIOptions
  <$> option readHost
        (  long "host"
        <> short 'H'
        <> metavar "HOST:PORT"
        <> help "Host and port where ollama serve is running"
        <> showDefault
        <> value defaultHost
        )
  -- NOTE: The inner <$> is for Maybe, the outer <$> is for Parser
  <*> ( (System . pack <$>) <$> optional ( strOption
        (  long "system"
        <> short 's'
        <> metavar "STR"
        <> help "System parameter"
        )
      ))
  <*> ( convertOptions <$> many ( strOption
        (  long "option"
        <> short 'o'
        <> metavar "KEY:VALUE"
        <> help "Options for the LLM, repeat for each option pair. See LLM OPTIONS below"
        )
      ))
  <*> ( Stream <$> switch
        (  long "stream"
        <> help "Response will be returned as a stream of objects"
        )
      )
  <*> ( RawOutput <$> switch
        (  long "raw-output"
        <> short 'r'
        <> help "Output the entire JSON response from the LLM"
        )
      )
  <*> ( Verbose <$> switch
        (  long "verbose"
        <> short 'v'
        <> help "Enable verbose output. Note: All logging goes to stderr"
        )
      )
  <*> ( Model . pack <$> argument str
        (  metavar "MODEL_ID"
        <> help "Model identifier, see available models with `ollama list`"
        )
      )


versionHelper :: String -> Parser (a -> a)
versionHelper progName =
  infoOption (formatToString (s %+ s) progName (showVersion version)) $ mconcat
  [ long "version"
  , help "Show version information"
  , hidden
  ]


parseOpts :: IO CLIOptions
parseOpts = do
  pn <- getProgName
  execParser $ info (parser <**> helper <**> versionHelper pn)
    (  header (formatToString (s %+ "- Command line tool for interacting with an ollama server") pn)
    <> footer'
    )


footer' :: InfoMod a
footer' = footerDoc . Just . pretty . format content . showVersion $ version
  where content = [here|OVERVIEW

This software expects the prompt string on STDIN and will respond with the LLM
response on STDOUT

example usage

    $ echo "Why is the sky blue?" | genai some-fancy-model

WHY WAS THIS DONE?

ollama ships with command-line ability to submit prompts and do completions:

    $ ollama MODEL [PROMPT] ...

but as far as I can see there's no easy way to use this method to change
options like the temperature and seed. This software allows greater control
over the options.

    $ echo "Tell me a story" | genai some-other-model -o temperature:0.2

LLM OPTIONS

Some settings are communicated to the LLM as a map of options. Use them like this

    -o temperature:0.2

Some useful options

    option name   description                                                   default
    -------------------------------------------------------------------------------------
    seed          Sets the random number seed to use for generation. Setting    0
                  this to a specific number will make the model generate the
                  same text for the same prompt.
    temperature   The temperature of the model. Increasing the temperature      0.8
                  will make the model answer more creatively.
                  Range 0.0 to 1.0
    top_k         Reduces the probability of generating nonsense. A higher      40
                  value (e.g. 100) will give more diverse answers, while a
                  lower value (e.g. 10) will be more conservative.
                  Range 0 to 100
    top_p         Works together with top-k. A higher value (e.g., 0.95) will   0.9
                  lead to more diverse text, while a lower value (e.g., 0.5)
                  will generate more focused and conservative text.
                  Range unknown, guessing 0.0 to 1.0

For the complete list of LLM options, see

https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values

Version|] %+ s %+ " Dino Morelli <dino@ui3.info>"