gym-http-api (empty) → 0.1.0.0
raw patch · 10 files changed
+496/−0 lines, 10 filesdep +aesondep +basedep +exceptionssetup-changed
Dependencies added: aeson, base, exceptions, gym-http-api, http-client, servant, servant-client, servant-lucid, text, unordered-containers
Files
- CHANGELOG.md +6/−0
- LICENSE +21/−0
- README.md +15/−0
- Setup.hs +2/−0
- TODO.md +6/−0
- examples/Agent.hs +80/−0
- gym-http-api.cabal +62/−0
- src/OpenAI/Gym.hs +17/−0
- src/OpenAI/Gym/API.hs +120/−0
- src/OpenAI/Gym/Data.hs +167/−0
+ CHANGELOG.md view
@@ -0,0 +1,6 @@+# Version 0.1.0.0++[tag v0.1.0.0](https://github.com/stites/gym-http-api/releases/tag/v0.1.0.0)++- Initial release. API contains basic environments and Aeson-based functions.+
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License++Copyright (c) 2016 OpenAI (http://openai.com)++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ README.md view
@@ -0,0 +1,15 @@+# Haskell Binding for the OpenAI gym open-source library++To run the example agent:++```+stack build && stack exec example+```++This library provides a servant-based REST client to the gym open-source library.+[openai/gym-http-api][openai] itself provides a [python-based REST server][flask]+to the gym open-source library, allowing development in languages other than python.++[openai]:https://github.com/openai/gym-http-api+[flask]:https://github.com/openai/gym-http-api/blob/master/gym_http_server.py+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ TODO.md view
@@ -0,0 +1,6 @@+## Checklist+- [ ] Add environment variable functionality to obtain the API key+- [ ] Optimization (lagging can be detected while running the example agent)+- [ ] Test suite++
+ examples/Agent.hs view
@@ -0,0 +1,80 @@+-------------------------------------------------------------------------------+-- |+-- Module : Main+-- License : MIT+-- Stability : experimental+-- Portability: non-portable+--+-- Example of how to build an agent using OpenAI.Gym.Client+-------------------------------------------------------------------------------+{-# LANGUAGE NamedFieldPuns #-}+module Main where++import Prelude+import Control.Monad (replicateM_, when)+import Control.Monad.Catch+import Control.Exception.Base++import OpenAI.Gym+import Servant.Client+import Network.HTTP.Client+++main :: IO ()+main = do+ manager <- newManager defaultManagerSettings+ out <- runClientM example (ClientEnv manager url)+ case out of+ Left err -> print err+ Right ok -> print ok++ where+ url :: BaseUrl+ url = BaseUrl Http "localhost" 5000 ""+++example :: ClientM ()+example = do+ inst <- envCreate CartPoleV0+ Monitor{directory} <- withMonitor inst $+ replicateM_ episodeCount (agent inst)++ -- Upload to the scoreboard.+ -- TODO: Implement environment variable support.+ upload (Config directory "algo" "")++ where+ episodeCount :: Int+ episodeCount = 100+++agent :: InstID -> ClientM ()+agent inst = do+ envReset inst+ go 0 False+ where+ maxSteps :: Int+ maxSteps = 200++ reward :: Int+ reward = 0++ go :: Int -> Bool -> ClientM ()+ go x done = do+ Action a <- envActionSpaceSample inst+ Outcome ob reward done _ <- envStep inst (Step a True)+ when (not done && x < 200) $ go (x + 1) done+++withMonitor :: InstID -> ClientM () -> ClientM Monitor+withMonitor inst agent = do+ envMonitorStart inst configs+ agent+ envMonitorClose inst+ return configs+ where+ configs :: Monitor+ configs = Monitor "/tmp/random-agent-results" True False False+++
+ gym-http-api.cabal view
@@ -0,0 +1,62 @@+-- This file has been generated from package.yaml by hpack version 0.18.1.+--+-- see: https://github.com/sol/hpack++name: gym-http-api+version: 0.1.0.0+synopsis: REST client to the gym-http-api project+description: This library provides a REST client to the gym open-source library. gym-http-api itself provides a <https://github.com/openai/gym-http-api/blob/master/gym_http_server.py python-based REST> server to the gym open-source library, allowing development in languages other than python.+ .+ Note that the <https://github.com/openai/gym-http-api/ openai/gym-http-api> is a monorepo of all language-clients. This hackage library tracks <https://github.com/stites/gym-http-api/ stites/gym-http-api> which is the actively-maintained haskell fork.+category: Web, Learning Environments+homepage: https://github.com/stites/gym-http-api#readme+bug-reports: https://github.com/stites/gym-http-api/issues+author: Daniel Lucsanszky, Sam Stites+maintainer: dl3913@ic.ac.uk, sam@stites.io+license: MIT+license-file: LICENSE+build-type: Simple+cabal-version: >= 1.10++extra-source-files:+ CHANGELOG.md+ README.md+ TODO.md++source-repository head+ type: git+ location: https://github.com/stites/gym-http-api+ subdir: binding-hs++library+ hs-source-dirs:+ src+ default-extensions: OverloadedStrings+ build-depends:+ base >= 4.7 && < 5+ , servant-client >= 0.9+ , aeson >= 1.0+ , unordered-containers >= 0.2+ , servant >= 0.9+ , servant-lucid >= 0.7+ , text >= 1.2+ exposed-modules:+ OpenAI.Gym+ OpenAI.Gym.API+ OpenAI.Gym.Data+ other-modules:+ Paths_gym_http_api+ default-language: Haskell2010++executable example+ main-is: Agent.hs+ hs-source-dirs:+ examples+ default-extensions: OverloadedStrings+ build-depends:+ base >= 4.7 && < 5+ , servant-client >= 0.9+ , gym-http-api+ , http-client >= 0.5+ , exceptions >= 0.8+ default-language: Haskell2010
+ src/OpenAI/Gym.hs view
@@ -0,0 +1,17 @@+-------------------------------------------------------------------------------+-- |+-- Module : OpenAI.Gym+-- License : MIT+-- Stability : experimental+-- Portability: non-portable+--+-- re-exports of @OpenAI.Gym.API@ and @OpenAI.Gym.Data@+-------------------------------------------------------------------------------+module OpenAI.Gym+ ( module OpenAI.Gym.API+ , module OpenAI.Gym.Data+ ) where++import OpenAI.Gym.API+import OpenAI.Gym.Data+
+ src/OpenAI/Gym/API.hs view
@@ -0,0 +1,120 @@+-------------------------------------------------------------------------------+-- |+-- Module : OpenAI.Gym.API+-- License : MIT+-- Stability : experimental+-- Portability: non-portable+--+-- Servant-client functions to interact with the flask server from+-- <https://github.com/openai/gym-http-api/ openai/gym-http-api>.+-------------------------------------------------------------------------------+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeOperators #-}+{-# OPTIONS_GHC -Wno-orphans #-} --for MimeUnrender HTML ()+module OpenAI.Gym.API (+ -- * Environment functions+ envCreate+ , envListAll+ , envReset+ , envStep+ , envActionSpaceInfo+ , envActionSpaceSample+ , envActionSpaceContains+ , envObservationSpaceInfo+ , envMonitorStart+ , envMonitorClose+ , envClose+ -- * Http-server commands+ , upload+ , shutdownServer+ -- * Servant code+ , gymAPI+ ) where++import Data.Aeson (Object)+import Data.Proxy (Proxy(..))+import Servant.API+import Servant.HTML.Lucid (HTML)+import Servant.Client (ClientM, client)++import OpenAI.Gym.Data++type GymAPI+ = "v1" :> ( "envs" :> ( ReqBody '[JSON] GymEnv :> Post '[JSON] InstID+ :<|> Get '[JSON] Environment+ :<|> Capture "instance_id" InstID :> "reset" :> Post '[JSON] Observation+ :<|> Capture "instance_id" InstID :> "step" :> ReqBody '[JSON] Step :> Post '[JSON] Outcome+ :<|> Capture "instance_id" InstID :> "action_space" :> Get '[JSON] Info+ :<|> Capture "instance_id" InstID :> "action_space" :> "sample" :> Get '[JSON] Action+ :<|> Capture "instance_id" InstID :> "action_space" :> "contains" :> Capture "x" Int :> Get '[JSON] Object+ :<|> Capture "instance_id" InstID :> "observation_space" :> Get '[JSON] Info+ :<|> Capture "instance_id" InstID :> "monitor" :> "start" :> ReqBody '[JSON] Monitor :> Post '[HTML] ()+ :<|> Capture "instance_id" InstID :> "monitor" :> "close" :> Post '[HTML] ()+ :<|> Capture "instance_id" InstID :> "close" :> Post '[HTML] ())+ :<|> "upload" :> ReqBody '[JSON] Config :> Post '[HTML] ()+ :<|> "shutdown" :> Post '[HTML] ())+++-- | Proxy type for the full servant-client representation of the gym http api.+gymAPI :: Proxy GymAPI+gymAPI = Proxy+++-- | Create an instance of the specified environment (@POST \/v1\/envs\/@)+envCreate :: GymEnv -> ClientM InstID++-- | List all environments running on the server (@GET \/v1\/envs\/@)+envListAll :: ClientM Environment++-- | Reset the state of the environment and return an initial observation. (@POST \/v1\/envs\/<instance_id>\/reset\/@)+envReset :: InstID -> ClientM Observation++-- | Step though an environment using an action. (@POST \/v1\/envs\/<instance_id>\/step\/@)+envStep :: InstID -> Step -> ClientM Outcome++-- | Get information (name and dimensions\/bounds) of the env's action_space (@GET \/v1\/envs\/<instance_id>\/action_space\/@)+envActionSpaceInfo :: InstID -> ClientM Info++-- | Sample randomly from the env's action_space (@GET \/v1\/envs\/<instance_id>\/action_space\/sample@)+envActionSpaceSample :: InstID -> ClientM Action++-- | Check to see if a value is valid in the env's action_space (@GET \/v1\/envs\/<instance_id>\/action_space\/contains\/<x>@)+envActionSpaceContains :: InstID -> Int -> ClientM Object++-- | Get information (name and dimensions\/bounds) of the env's observation_space (@GET \/v1\/envs\/<instance_id>\/observation_space\/@)+envObservationSpaceInfo :: InstID -> ClientM Info++-- | Start monitoring (@POST \/v1\/envs\/<instance_id>\/monitor\/start\/@)+envMonitorStart :: InstID -> Monitor -> ClientM ()++-- | Flush all monitor data to disk (@POST \/v1\/envs\/<instance_id>\/monitor\/close\/@)+envMonitorClose :: InstID -> ClientM ()++-- | Stop the environment (@POST \/v1\/envs\/<instance_id>\/close\/@)+envClose :: InstID -> ClientM ()++-- | Upload results to OpenAI's servers (@POST \/v1\/upload\/@)+upload :: Config -> ClientM ()++-- | Request a server shutdown (@POST \/v1\/shutdown\/@)+shutdownServer :: ClientM ()+++(envCreate+ :<|> envListAll+ :<|> envReset+ :<|> envStep+ :<|> envActionSpaceInfo+ :<|> envActionSpaceSample+ :<|> envActionSpaceContains+ :<|> envObservationSpaceInfo+ :<|> envMonitorStart+ :<|> envMonitorClose+ :<|> envClose)+ :<|> upload+ :<|> shutdownServer+ = client gymAPI++instance MimeUnrender HTML () where+ mimeUnrender _ _ = return ()
+ src/OpenAI/Gym/Data.hs view
@@ -0,0 +1,167 @@+-------------------------------------------------------------------------------+-- |+-- Module : OpenAI.Gym.Data+-- License : MIT+-- Stability : experimental+-- Portability: non-portable+--+-- Aeson-based data types to be returned by "OpenAI.Gym.API"+-------------------------------------------------------------------------------+{-# LANGUAGE DeriveGeneric #-}+module OpenAI.Gym.Data+ ( GymEnv (..)+ , InstID (..)+ , Environment (..)+ , Observation (..)+ , Step (..)+ , Outcome (..)+ , Info (..)+ , Action (..)+ , Monitor (..)+ , Config (..)+ ) where++import Data.Aeson (ToJSON(..), FromJSON(..), Value(..), Object, (.=), (.:), object)+import Data.Aeson.Types (Parser)+import Data.HashMap.Strict (HashMap)+import Data.Text (Text)+import GHC.Generics (Generic)+import Servant.API (ToHttpApiData(..))+import qualified Data.Text as T+import qualified Data.Aeson as A++-- | Classic Control Environments+data GymEnv+ = CartPoleV0 -- ^ Balance a pole on a cart (for a short time).+ | CartPoleV1 -- ^ Balance a pole on a cart.+ | AcrobotV1 -- ^ Swing up a two-link robot.+ | MountainCarV0 -- ^ Drive up a big hill.+ | MountainCarContinuousV0 -- ^ Drive up a big hill with continuous control.+ | PendulumV0 -- ^ Swing up a pendulum.++ -- Toy text games+ | FrozenLakeV0 -- ^ Swing up a pendulum.++ -- | Atari Games+ | PongRamV0 -- ^ Maximize score in the game Pong, with RAM as input+ | PongV0 -- ^ Maximize score in the game Pong+ deriving (Eq, Enum, Ord)+++instance Show GymEnv where+ show CartPoleV0 = "CartPole-v0"+ show CartPoleV1 = "CartPole-v1"+ show AcrobotV1 = "Acrobot-v1"+ show MountainCarV0 = "MountainCar-v0"+ show MountainCarContinuousV0 = "MountainCarContinuous-v0"+ show PendulumV0 = "Pendulum-v0"+ show FrozenLakeV0 = "FrozenLake-v0"+ show PongRamV0 = "Pong-ram-v0"+ show PongV0 = "Pong-v0"++instance ToJSON GymEnv where+ toJSON env = object [ "env_id" .= show env ]++-- | a short identifier (such as '3c657dbc') for the created environment instance.+-- The instance_id is used in future API calls to identify the environment to be manipulated.+newtype InstID = InstID { getInstID :: Text }+ deriving (Eq, Show, Generic)++instance ToHttpApiData InstID where+ toUrlPiece (InstID i) = i++instance ToJSON InstID where+ toJSON (InstID i) = toSingleton "instance_id" i++instance FromJSON InstID where+ parseJSON = parseSingleton InstID "instance_id"++-- | a mapping of instance_id to env_id (e.g. {'3c657dbc': 'CartPole-v0'}) for every env on the server+newtype Environment = Environment { all_envs :: HashMap Text Text }+ deriving (Eq, Show, Generic)++instance ToJSON Environment+instance FromJSON Environment++-- | The agent's observation of the current environment+newtype Observation = Observation { getObservation :: Value }+ deriving (Eq, Show, Generic)++instance ToJSON Observation where+ toJSON (Observation v) = toSingleton "observation" v++instance FromJSON Observation where+ parseJSON = parseSingleton Observation "observation"++-- | An action to take in the environment and whether or not to render that change+data Step = Step+ { action :: !Value+ , render :: !Bool+ } deriving (Eq, Generic, Show)++instance ToJSON Step++-- | The result of taking a step in an environment+data Outcome = Outcome+ { observation :: !Value -- ^ agent's observation of the current environment+ , reward :: !Double -- ^ amount of reward returned after previous action+ , done :: !Bool -- ^ whether the episode has ended+ , info :: !Object -- ^ a dict containing auxiliary diagnostic information+ } deriving (Eq, Show, Generic)++instance ToJSON Outcome+instance FromJSON Outcome++-- | A dict containing auxiliary diagnostic information+newtype Info = Info { getInfo :: Object }+ deriving (Eq, Show, Generic)++instance ToJSON Info where+ toJSON (Info v) = toSingleton "info" v++instance FromJSON Info where+ parseJSON = parseSingleton Info "info"++-- | An action to take in the environment+newtype Action = Action { getAction :: Value }+ deriving (Eq, Show, Generic)++instance ToJSON Action where+ toJSON (Action v) = toSingleton "action" v++instance FromJSON Action where+ parseJSON = parseSingleton Action "action"++-- | Parameters used to start a monitoring session.+data Monitor = Monitor+ { directory :: !Text -- ^ directory to use for monitoring+ , force :: !Bool -- ^ Clear out existing training data from this directory (by deleting+ -- every file prefixed with "openaigym.") (default=False)+ , resume :: !Bool -- ^ Retain the training data already in this directory, which will be+ -- merged with our new data. (default=False)+ , video_callable :: !Bool -- ^ video_callable parameter from the native env.monitor.start function+ } deriving (Generic, Eq, Show)++instance ToJSON Monitor++-- | Parameters used to upload a monitored session to OpenAI's servers+data Config = Config+ { training_dir :: !Text -- ^ A directory containing the results of a training run.+ , algorithm_id :: !Text -- ^ An arbitrary string indicating the paricular version of the+ -- algorithm (including choices of parameters) you are running.+ -- (default=None)+ , api_key :: !Text -- ^ Your OpenAI API key+ } deriving (Generic, Eq, Show)++instance ToJSON Config+++-- | helper to parse a singleton object from aeson+parseSingleton :: FromJSON a => (a -> b) -> Text -> Value -> Parser b+parseSingleton fn f (Object v) = fn <$> v .: f+parseSingleton fn f _ = mempty++-- | convert a value into a singleton object+toSingleton :: ToJSON a => Text -> a -> Value+toSingleton f a = object [ f .= toJSON a ]+