packages feed

llm-simple-0.1.0.1: src/LLM/Generate/GenerateObject.hs

module LLM.Generate.GenerateObject
  ( genObject,
    genObjectUntyped,
  )
where

import Autodocodec qualified as AC
import Autodocodec.Schema (jsonSchemaVia)
import Data.Aeson (Value)
import Data.Aeson qualified as AE
import Data.Maybe (fromMaybe)
import Data.Text qualified as T
import LLM.Core.Abort (isAbortedMaybe)
import LLM.Core.ProviderUtils (stripBoundsAndComments)
import LLM.Core.Types (LLMGateway (..), LLMObjectResult)
import LLM.Core.Usage (Usage (..), emptyUsage)
import LLM.Generate.GenerateUtils
  ( callWithRetryTimeout,
    mkRequest,
    usageWithModelCost,
    withModelFallbacks,
  )
import LLM.Generate.ModelConfig (ModelConfig (mcGateway), ModelWithFallbacks)
import LLM.Generate.Types
  ( GenRequest (grAbortSignal, grLLMHooks),
    GeneratableObject,
    GenerateError (..),
    GenerateErrorResult (..),
    GenerateResult,
  )

-- | Generate an object.
-- Tools are not supported, make sure to set grTools = [] in the GenRequest.
genObject ::
  (GeneratableObject t) =>
  GenRequest ->
  ModelWithFallbacks ->
  IO (Either GenerateErrorResult (t, Usage))
genObject = genObjectInternal AC.codec

genObjectInternal ::
  (GeneratableObject t) =>
  AC.JSONCodec t ->
  GenRequest ->
  ModelWithFallbacks ->
  IO (Either GenerateErrorResult (t, Usage))
genObjectInternal codec gr models = do
  let jsonschema = stripBoundsAndComments $ AE.toJSON $ jsonSchemaVia codec
  res <- genObjectUntyped gr models jsonschema
  case res of
    Left e -> pure (Left e)
    Right (v, u) -> do
      case AE.fromJSON v of
        AE.Error e ->
          pure $
            Left $
              GenerateErrorResult
                (GErrParseObjectError $ "Can't decode object returned from generateObjectUntyped" <> T.pack (show e))
                []
                u
        AE.Success a -> pure $ Right (a, u)

-- | Generate an object, using the provided schema.
-- Tools are not supported, make sure to set grTools = [] in the GenRequest.
genObjectUntyped ::
  GenRequest ->
  ModelWithFallbacks ->
  Value ->
  IO (Either GenerateErrorResult (Value, Usage))
genObjectUntyped gr models schema = do
  aborted <- isAbortedMaybe gr.grAbortSignal
  if aborted
    then do
      let errResult = GenerateErrorResult GErrAborted [] emptyUsage
      pure $ Left errResult
    else do
      result <- callObjectWithFallbacks gr models schema
      case result of
        Left err -> do
          let errResult = GenerateErrorResult err [] emptyUsage
          pure $ Left errResult
        Right (value, usage) -> do
          pure $ Right (value, usage)

callObject ::
  GenRequest ->
  ModelConfig ->
  Value ->
  IO LLMObjectResult
callObject gr mc schema =
  callWithRetryTimeout gr mc $
    mc.mcGateway.gwGenerateObject
      gr.grLLMHooks
      schema
      (mkRequest gr mc)

callObjectWithFallbacks ::
  GenRequest ->
  ModelWithFallbacks ->
  Value ->
  IO (GenerateResult (Value, Usage))
callObjectWithFallbacks gr models schema =
  withModelFallbacks gr models $ \mc -> do
    r <- callObject gr mc schema
    case r of
      Left err -> pure $ Left err
      Right (v, mbu) ->
        pure $ Right (v, usageWithModelCost mc (fromMaybe emptyUsage mbu))