1
1
{-# LANGUAGE DataKinds #-}
2
- {-# LANGUAGE DeriveAnyClass #-}
3
- {-# LANGUAGE DerivingStrategies #-}
4
- {-# LANGUAGE LambdaCase #-}
5
- {-# LANGUAGE OverloadedStrings #-}
6
- {-# LANGUAGE QuasiQuotes #-}
7
- {-# LANGUAGE TypeOperators #-}
8
2
9
3
module Main (main ) where
10
4
11
- import Control.Monad.IO.Class (MonadIO , liftIO )
12
- import Control.Monad.Trans.Except
13
- import Data.Aeson (FromJSON , ToJSON , (.=) )
14
- import Data.Aeson qualified as Aeson
15
- import Data.Aeson.Types qualified as Aeson
16
- import Data.HashMap.Strict qualified as HashMap
17
- import Data.Map qualified as Map
18
- import Data.Scientific (toRealFloat )
19
- import Data.String.Interpolate (__i )
20
- import Data.Text qualified as Text
21
- import Explainable.MathLang
22
- import GHC.Generics
23
5
import Network.Wai
24
6
import Network.Wai.Handler.Warp
25
7
import Network.Wai.Logger
26
8
import Options
27
- import Options.Applicative
9
+ import Options.Applicative as Opts
10
+ import Schema
28
11
import Servant
29
- import System.Timeout (timeout )
12
+ import Servant.Swagger.UI (SwaggerSchemaUI , swaggerSchemaUIServer )
13
+ import Server
30
14
31
15
-- ----------------------------------------------------------------------------
32
16
-- Option Parser
33
17
-- ----------------------------------------------------------------------------
34
18
35
19
opts :: ParserInfo Options
36
20
opts =
37
- info
21
+ Opts. info
38
22
(optionsParser <**> helper)
39
23
( fullDesc
40
24
<> progDesc " Serve a Web Service for interacting with a MathLang evaluator"
41
25
<> header " explainable - A web server for MathLang"
42
26
)
43
27
44
- -- ----------------------------------------------------------------------------
45
- -- Servant API
46
- -- ----------------------------------------------------------------------------
47
-
48
- type Api = FunctionApi
49
-
50
- type FunctionApi =
51
- " functions"
52
- :> (Functions :<|> FunctionsCrud )
53
-
54
- type Functions = Get '[JSON ] [SimpleFunction ]
55
-
56
- type FunctionsCrud =
57
- Capture " name" String
58
- :> ( ReqBody '[JSON ] Attributes :> Post '[JSON ] SimpleResponse
59
- :<|> Get '[JSON ] Function
60
- )
61
-
62
- data FlatValue
63
- = Number Double
64
- | Boolean Bool
65
- deriving (Show , Read , Ord , Eq , Generic )
66
-
67
- instance FromJSON FlatValue where
68
- parseJSON (Aeson. Number sci) = pure $ Number $ toRealFloat sci
69
- parseJSON (Aeson. Bool b) = pure $ Boolean b
70
- parseJSON o =
71
- Aeson. parseFail $ " Unexpected value, expected Number or Bool but got: " <> show o
72
-
73
- newtype Attributes = Attributes
74
- { mkAttributes :: Map. Map String FlatValue
75
- }
76
- deriving (Show , Read , Ord , Eq , Generic )
77
- deriving newtype (FromJSON )
78
-
79
- data SimpleResponse
80
- = SimpleResponse Double
81
- | Insufficient String
82
- deriving (Show , Read , Ord , Eq , Generic )
83
- deriving anyclass (FromJSON , ToJSON )
84
-
85
28
-- ----------------------------------------------------------------------------
86
29
-- Main Application and wiring
87
30
-- ----------------------------------------------------------------------------
@@ -93,180 +36,14 @@ main = do
93
36
let settings = setPort port $ setLogger aplogger defaultSettings
94
37
runSettings settings app
95
38
96
- app :: Application
97
- app = serve (Proxy @ Api ) handler
98
-
99
- -- ----------------------------------------------------------------------------
100
- -- Web Service Handlers
101
- -- ----------------------------------------------------------------------------
102
-
103
- handler :: Server Api
104
- handler =
105
- handlerFunctions
106
- :<|> ( \ name ->
107
- handlerFunction name
108
- :<|> handlerParameters name
109
- )
110
-
111
- handlerFunctions :: Handler [SimpleFunction ]
112
- handlerFunctions = do
113
- pure $ fmap (toSimpleFunction . snd ) $ Map. elems functions
114
- where
115
- toSimpleFunction s =
116
- SimpleFunction
117
- { simpleName = name s
118
- , simpleDescription = description s
119
- }
120
-
121
- handlerFunction :: String -> Attributes -> Handler SimpleResponse
122
- handlerFunction name query = do
123
- case Map. lookup name functions of
124
- Nothing -> throwError err404
125
- Just (scenario, _) ->
126
- case runExcept $ fromParams query of
127
- Left err ->
128
- pure $ Insufficient err
129
- Right s -> do
130
- response <- timeoutAction $ runScenario s scenario
131
- pure $ SimpleResponse response
39
+ type ApiWithSwagger =
40
+ SwaggerSchemaUI " swagger-ui" " swagger.json"
41
+ :<|> Api
132
42
133
- handlerParameters :: String -> Handler Function
134
- handlerParameters name = case Map. lookup name functions of
135
- Nothing -> throwError err404
136
- Just (_, scenario) -> pure scenario
43
+ appWithSwagger :: Servant. Server ApiWithSwagger
44
+ appWithSwagger =
45
+ swaggerSchemaUIServer serverOpenApi
46
+ :<|> handler
137
47
138
- timeoutAction :: IO b -> Handler b
139
- timeoutAction act =
140
- liftIO (timeout (seconds 5 ) act) >>= \ case
141
- Nothing -> throwError err505
142
- Just r -> pure r
143
- where
144
- seconds n = 1_000_000 * n
145
-
146
- -- ----------------------------------------------------------------------------
147
- -- API specification for LLMs
148
- -- ----------------------------------------------------------------------------
149
-
150
- data SimpleFunction = SimpleFunction
151
- { simpleName :: Text. Text
152
- , simpleDescription :: Text. Text
153
- }
154
- deriving (Show , Read , Ord , Eq , Generic )
155
-
156
- data Function = Function
157
- { name :: Text. Text
158
- , description :: Text. Text
159
- , parameters :: Parameters
160
- }
161
- deriving (Show , Read , Ord , Eq , Generic )
162
-
163
- newtype Parameters = Parameters Properties
164
- deriving (Show , Read , Ord , Eq , Generic )
165
-
166
- instance ToJSON SimpleFunction where
167
- toJSON (SimpleFunction n desc) =
168
- Aeson. object
169
- [ " type" .= Aeson. String " function"
170
- , " function"
171
- .= Aeson. object
172
- [ " name" .= Aeson. String n
173
- , " description" .= Aeson. String desc
174
- ]
175
- ]
176
-
177
- instance ToJSON Function where
178
- toJSON (Function n desc params) =
179
- Aeson. object
180
- [ " type" .= Aeson. String " function"
181
- , " function"
182
- .= Aeson. object
183
- [ " name" .= Aeson. String n
184
- , " description" .= Aeson. String desc
185
- , " parameters" .= params
186
- ]
187
- ]
188
-
189
- instance ToJSON Parameters where
190
- toJSON (Parameters props) =
191
- Aeson. object
192
- [ " type" .= Aeson. String " object"
193
- , " properties" .= props
194
- ]
195
-
196
- newtype Properties = Properties
197
- { mkProperties :: Map. Map String Parameter
198
- }
199
- deriving (Show , Read , Ord , Eq , Generic )
200
- deriving newtype (ToJSON )
201
-
202
- data Parameter = Parameter
203
- { parameterType :: String
204
- , parameterEnum :: [String ]
205
- , parameterDescription :: String
206
- }
207
- deriving (Show , Read , Ord , Eq , Generic )
208
-
209
- instance ToJSON Parameter where
210
- toJSON (Parameter ty enum desc) =
211
- Aeson. object
212
- [ " type" .= ty
213
- , " enum" .= enum
214
- , " description" .= desc
215
- ]
216
-
217
- -- ----------------------------------------------------------------------------
218
- -- Example Rules
219
- -- ----------------------------------------------------------------------------
220
-
221
- runScenario :: (MonadIO m ) => MyState -> Expr Double -> m Double
222
- runScenario s scenario = do
223
- (res, _, _, _) <- liftIO $ xplainF () s scenario
224
- pure res
225
-
226
- fromParams :: Attributes -> Except String MyState
227
- fromParams attrs = do
228
- let (valueMap, predMap) = Map. mapEither go (mkAttributes attrs)
229
- pure $
230
- emptyState
231
- { symtabF = HashMap. fromList $ Map. toList valueMap
232
- , symtabP = HashMap. fromList $ Map. toList predMap
233
- }
234
- where
235
- go (Number n) = Left $ Val Nothing n
236
- go (Boolean b) = Right $ PredVal Nothing b
237
-
238
- functions :: Map. Map String (Expr Double , Function )
239
- functions =
240
- Map. fromList
241
- [ (" compute_qualifies" , (personQualifies, personQualifiesFunction))
242
- ]
243
-
244
- personQualifies :: Expr Double
245
- personQualifies =
246
- " qualifies"
247
- @|= MathPred
248
- ( (getvar " walks" ) |&& ((getvar " drinks" ) ||| (getvar " eats" ))
249
- )
250
-
251
- personQualifiesFunction :: Function
252
- personQualifiesFunction =
253
- Function
254
- " compute_qualifies"
255
- [__i |Determines if a person qualifies for the purposes of the rule.
256
- The input object describes the person's properties in the primary parameters: walks, eats, drinks.
257
- Secondary parameters can be given which are sufficient to determine some of the primary parameters.
258
- A person drinks whether or not they consume an alcoholic or a non-alcoholic beverage, in part or in whole;
259
- those specific details don't really matter.
260
- The output of the function can be either a request for required information;
261
- a restatement of the user input requesting confirmation prior to function calling;
262
- or a Boolean answer with optional explanation summary.
263
- |]
264
- $ Parameters
265
- $ Properties
266
- $ Map. fromList
267
- [ (" walks" , Parameter " string" [" true" , " false" , " unknown" ] " Did the person walk?" )
268
- , (" eats" , Parameter " string" [" true" , " false" , " unknown" ] " Did the person eat?" )
269
- , (" drinks" , Parameter " string" [" true" , " false" , " unknown" ] " Did the person drink?" )
270
- , (" beverage type" , Parameter " string" [" alcoholic" , " non-alcoholic" , " unknown" ] " Did the person drink an alcoholic beverage?" )
271
- , (" in whole" , Parameter " string" [" true" , " false" , " unknown" ] " Did the person drink all of the beverage?" )
272
- ]
48
+ app :: Application
49
+ app = serve (Proxy @ ApiWithSwagger ) appWithSwagger
0 commit comments