packages feed

baikai-openai 0.1.0.0 → 0.1.1.0

raw patch · 3 files changed

+75/−7 lines, 3 filesdep ~baikaiPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: baikai

API changes (from Hackage documentation)

+ Baikai.Provider.OpenAI.Api: mapRequest :: Model -> Context -> Options -> Either Text CreateChatCompletion

Files

baikai-openai.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name:          baikai-openai-version:       0.1.0.0+version:       0.1.1.0 synopsis:      OpenAI providers for the baikai abstraction description:   Wraps the openai Haskell package as a Baikai Provider for OpenAI's Chat Completions API.@@ -37,7 +37,7 @@    build-depends:     , aeson-    , baikai             ^>=0.1.0+    , baikai             ^>=0.1.1     , base               >=4.20   && <5     , base64-bytestring     , bytestring@@ -61,12 +61,14 @@   hs-source-dirs: test   main-is:        Main.hs   build-depends:-    , baikai         ^>=0.1.0+    , aeson+    , baikai         ^>=0.1.1     , baikai-openai     , base           >=4.20   && <5     , bytestring     , generic-lens     , lens           ^>=5.3+    , openai     , streamly-core  >=0.3    && <0.5     , tasty     , tasty-hunit
src/Baikai/Provider/OpenAI/Api.hs view
@@ -30,6 +30,7 @@   ( register,     registerWithRegistry,     openaiChatStream,+    mapRequest,   ) where @@ -52,6 +53,7 @@     globalProviderRegistry,     registerApiProviderWith,   )+import Baikai.ResponseFormat (ResponseFormat (..)) import Baikai.StopReason qualified as Stop import Baikai.Stream (streamingComplete) import Baikai.Stream.Event@@ -95,6 +97,7 @@ import OpenAI.V1 qualified as OpenAI import OpenAI.V1.Chat.Completions qualified as Chat import OpenAI.V1.Models qualified as OpenAIModels+import OpenAI.V1.ResponseFormat qualified as RF import OpenAI.V1.Tool qualified as OpenAITool import OpenAI.V1.ToolCall qualified as ToolCall import Streamly.Data.Stream (Stream)@@ -726,6 +729,8 @@       toolChoiceField = fmap mkOpenAIToolChoice (opts ^. #toolChoice)       reasoningEffortField =         applyThinkingFormat compat (opts ^. #thinking)+      responseFormatField =+        fmap mkOpenAIResponseFormat (opts ^. #responseFormat)   pure     Chat._CreateChatCompletion       { Chat.messages = Vector.fromList (prefix <> body),@@ -734,7 +739,26 @@         Chat.temperature = opts ^. #temperature,         Chat.tools = toolsField,         Chat.tool_choice = toolChoiceField,-        Chat.reasoning_effort = reasoningEffortField+        Chat.reasoning_effort = reasoningEffortField,+        Chat.response_format = responseFormatField+      }++-- | Map a baikai 'ResponseFormat' onto the upstream OpenAI+-- 'RF.ResponseFormat'. 'JsonObject' becomes plain-JSON mode;+-- 'JsonSchema' becomes a named, optionally-strict schema. The+-- schema 'Value' is forwarded verbatim.+mkOpenAIResponseFormat :: ResponseFormat -> RF.ResponseFormat+mkOpenAIResponseFormat = \case+  JsonObject -> RF.JSON_Object+  JsonSchema {name = n, schema = s, strict = st} ->+    RF.JSON_Schema+      { RF.json_schema =+          RF.JSONSchema+            { RF.description = Nothing,+              RF.name = n,+              RF.schema = Just s,+              RF.strict = Just st+            }       }  -- | Map a 'Baikai.ThinkingLevel.ThinkingLevel' onto the OpenAI SDK's
test/Main.hs view
@@ -4,24 +4,66 @@ import Baikai.Provider.OpenAI.Api import Baikai.Provider.OpenAI.Interactive import Control.Lens ((&), (.~), (^.))+import Data.Aeson qualified as Aeson import Data.ByteString.Char8 qualified as BS8 import Data.Generics.Labels () import Data.Text qualified as Text import Data.Vector qualified as Vector+import OpenAI.V1.Chat.Completions qualified as Chat+import OpenAI.V1.ResponseFormat qualified as RF import Streamly.Data.Stream qualified as Stream import Test.Tasty (TestTree, defaultMain, testGroup)-import Test.Tasty.HUnit (assertBool, testCase, (@?=))+import Test.Tasty.HUnit (assertBool, assertFailure, testCase, (@?=))  main :: IO () main =   defaultMain $     testGroup-      "Baikai.Provider.OpenAI.Interactive"+      "Baikai.Provider.OpenAI"       [ commandRenderingTest,         promptRenderingTest,         compatDetectionTest,-        rejectsImageToolResultsTest+        rejectsImageToolResultsTest,+        responseFormatMappingTest       ]++-- | A 'JsonSchema' on 'Options.responseFormat' maps onto the+-- upstream OpenAI @response_format@ as a named, strict JSON schema,+-- forwarding the schema 'Value' verbatim. Pure: 'mapRequest' is+-- 'Either Text Chat.CreateChatCompletion'.+responseFormatMappingTest :: TestTree+responseFormatMappingTest =+  testCase "responseFormat JsonSchema maps onto OpenAI response_format" $ do+    let model =+          _Model+            & #modelId .~ "gpt-4o-mini"+            & #api .~ OpenAIChatCompletions+            & #provider .~ "openai"+        personSchema =+          Aeson.object+            [ "type" Aeson..= ("object" :: Text.Text),+              "properties"+                Aeson..= Aeson.object+                  [ "name" Aeson..= Aeson.object ["type" Aeson..= ("string" :: Text.Text)],+                    "age" Aeson..= Aeson.object ["type" Aeson..= ("integer" :: Text.Text)]+                  ],+              "required" Aeson..= (["name", "age"] :: [Text.Text]),+              "additionalProperties" Aeson..= False+            ]+        ctx = _Context+        opts =+          _Options+            & #responseFormat+              .~ Just (JsonSchema {name = "person", schema = personSchema, strict = True})+    case mapRequest model ctx opts of+      Left e -> assertFailure ("mapRequest failed: " <> Text.unpack e)+      Right req -> case Chat.response_format req of+        Just (RF.JSON_Schema {RF.json_schema = js}) -> do+          RF.name js @?= "person"+          RF.schema js @?= Just personSchema+          RF.strict js @?= Just True+          RF.description js @?= Nothing+        other -> assertFailure ("expected JSON_Schema, got: " <> show other)  commandRenderingTest :: TestTree commandRenderingTest =