binding-wl/gym_http_client.wl (268 lines of code) (raw):

BeginPackage["GymEnvironment`", {"GeneralUtilities`"}] EnvCreate; EnvClose; EnvListAll; EnvReset; EnvStep; EnvActionSpaceInfo; EnvActionSpaceSample; EnvActionSpaceContains; EnvObservationSpaceInfo; EnvMonitorStart; EnvMonitorClose; ShutdownGymServer; GymEnvironmentObject; GymUpload; Begin["`Private`"] $DefaultServer = "http://127.0.0.1:5000"; (*----------------------------------------------------------------------------*) (* Private functions *) (*************************) (* Factor out the commmon error handling routine *) $unknownError = Failure["UnknownError", <|"MessageTemplate" :> "Error of unknown type."|>]; gymSafeRequestExecute[req_HTTPRequest] := Module[ {res, body, msg}, res = Quiet @ URLRead[req]; If[FailureQ[res], Throw[res]]; body = Quiet @ Developer`ReadRawJSONString[res["Body"]]; (* sometimes, the body is a string that is not JSONizable, eg \"Server shutting down\". Need to handle this case. *) msg = If[FailureQ[body], Missing[], Lookup[body, "message"]]; If[(res["StatusCode"] =!= 200) && !MissingQ[msg], Throw @ Failure["ServerError", <| "MessageTemplate" :> StringTemplate["`Message`"], "MessageParameters" -> <|"Message" -> msg|> |>] ]; body ] gymSafeRequestExecute[_] := Throw[$unknownError] (*************************) gymPOSTRequest[server_String, route_String, data_Association] := gymSafeRequestExecute[ HTTPRequest[server <> route, <| "Body" -> Developer`WriteRawJSONString[data], Method -> "POST", "ContentType" -> "application/json" |> ] ] gymPOSTRequest[server_String, route_String] := gymPOSTRequest[server, route, <||>] gymGETRequest[server_String, route_String] := gymSafeRequestExecute[ HTTPRequest[server <> route, <| Method -> "GET", "ContentType" -> "application/json" |> ] ] (*************************) (* Make GymEnvironmentObject objects format nicely in notebooks *) DefineCustomBoxes[GymEnvironmentObject, e:GymEnvironmentObject[id_, name_, server_] :> Block[ {}, BoxForm`ArrangeSummaryBox[ GymEnvironmentObject, e, None, {BoxForm`SummaryItem[{"ID: ", id}] }, {}, StandardForm ]] ]; (*----------------------------------------------------------------------------*) (* Start of public API *) (*************************) SetUsage[" GymEnvironmentObject[id$]['ID'] returns the ID of the environment. GymEnvironmentObject[id$]['Name'] returns the name of the environment. GymEnvironmentObject[id$]['URL'] returns the URL of the server the environment \ is running on. "] GymEnvironmentObject[id_, _, _]["ID"] := id GymEnvironmentObject[_, name_, _]["Name"] := name GymEnvironmentObject[_, _, url_]["URL"] := url (*************************) SetUsage[" EnvListAll[] lists all environments running on the default server. EnvListAll[url$] lists environments given the specified server url$, where url$ is \ either a string or a URL object. "] EnvListAll[server_String] := Catch @ Module[ {res}, res = gymGETRequest[server, "/v1/envs/"]; res["all_envs"] ] EnvListAll[URL[server]] := EnvListAll[server] EnvListAll[] := EnvListAll[$DefaultServer]; (*************************) SetUsage[" EnvCreate[type$] creates an instance of the environment with string \ name type$ on the default server. EnvCreate[type$, url$] creates an environment on the specified server url$, \ where url$ is either a string or a URL object. "] EnvCreate[type_String, server_String] := Catch @ Module[ {res}, res = gymPOSTRequest[server, "/v1/envs/", <|"env_id" -> type|>]; GymEnvironmentObject[res["instance_id"], type, server] ] EnvCreate[type_String, URL[server_]] := EnvCreate[type, server] EnvCreate[type_String] := EnvCreate[type, $DefaultServer] (*************************) SetUsage[" EnvClose[GymEnvironmentObject[id$]] closes the environment.. "] EnvClose[ins_GymEnvironmentObject] := Catch @ Module[{}, gymPOSTRequest[ins["URL"], StringJoin["/v1/envs/", ins["ID"], "/close/"]]; ] (*************************) SetUsage[" EnvStep[GymEnvironmentObject[id$], act$] steps through an environment using \ an action act$. EnvReset[GymEnvironmentObject[id$]] must be called before the first \ call to EnvStep. EnvStep[GymEnvironmentObject[id$], act$, render$] displays the current state \ of the environment in a separate windows when render$ is True. "] EnvStep[ins_GymEnvironmentObject, action_, render_:False] := Catch @ Module[ {route, data}, route = StringJoin["/v1/envs/", ins["ID"], "/step/"]; data = <|"action" -> action, "render" -> render|>; gymPOSTRequest[ins["URL"], route, data] ] (*************************) SetUsage[" EnvReset[GymEnvironmentObject[id$]] resets the state of the environment and \ returns an initial observation. "] EnvReset[ins_GymEnvironmentObject] := Catch @ Module[ {route, res}, route = StringJoin["/v1/envs/", ins["ID"], "/reset/"]; res = gymPOSTRequest[ins["URL"], route]; res["observation"] ] (*************************) SetUsage[" EnvActionSpaceInfo[GymEnvironmentObject[id$]] returns an Association \ containing information (name and dimensions/bounds) of the environment's action space. "] EnvActionSpaceInfo[ins_GymEnvironmentObject] := Catch @ Module[ {route, res}, route = StringJoin["/v1/envs/", ins["ID"], "/action_space/"]; res = gymGETRequest[ins["URL"], route]; res["info"] ] (*************************) SetUsage[" EnvActionSpaceSample[GymEnvironmentObject[id$]] randomly samples an \ action from the action space. "] EnvActionSpaceSample[ins_GymEnvironmentObject] := Catch @ Module[ {route, res}, route = StringJoin["/v1/envs/", ins["ID"], "/action_space/sample"]; res = gymGETRequest[ins["URL"], route]; res["action"] ] (*************************) SetUsage[" EnvActionSpaceContains[GymEnvironmentObject[id$], act$] returns True if act$ is \ an element of the action space, otherwise False. "] EnvActionSpaceContains[ins_GymEnvironmentObject, act_] := Catch @ Module[ {route, res}, route = StringJoin["/v1/envs/", ins["ID"], "/action_space/contains/", ToString[act]]; res = gymGETRequest[ins["URL"], route]; res["member"] ] (*************************) SetUsage[" EnvObservationSpaceInfo[GymEnvironmentObject[id$]] gets information \ (name and dimensions/bounds) of the environments observation space. "] EnvObservationSpaceInfo[ins_GymEnvironmentObject] := Catch @ Module[ {route, res}, route = StringJoin["/v1/envs/", ins["ID"], "/observation_space/"]; res = gymGETRequest[ins["URL"], route]; res["info"] ] (*************************) SetUsage[" EnvMonitorStart[GymEnvironmentObject[id$], dir$] starts logging actions and \ environment states to a file stored in dir$. The following options are available: | 'Force' | False | Clear out existing training data from this directory \ (by deleting every file prefixed with 'openaigym.') | | 'Resume' | False | Retain the training data already in this directory, \ which will be merged with our new data. | | 'VideoCallable' | False | Not yet implemented. | "] Options[EnvMonitorStart] = { "Force" -> False, "Resume" -> False, "VideoCallable" -> False }; EnvMonitorStart[ins_GymEnvironmentObject, dir_, opts:OptionsPattern[]] := Catch @ Module[ {route, data}, data = <| "directory" -> If[Head[dir] === File, First[dir], dir], "force" -> OptionValue["Force"], "resume" -> OptionValue["Resume"], "video_callable" -> OptionValue["VideoCallable"] |>; route = StringJoin["/v1/envs/", ins["ID"], "/monitor/start/"]; gymPOSTRequest[ins["URL"], route, data]; (* don't return *) ] (*************************) SetUsage[" EnvMonitorClose[GymEnvironmentObject[id$]] flushes all monitor data to disk. "] EnvMonitorClose[ins_GymEnvironmentObject] := Catch @ Module[{}, gymPOSTRequest[ins["URL"], StringJoin["/v1/envs/", ins["ID"], "/monitor/close/"]]; ] (*************************) SetUsage[" GymUpload[dir$] uploads results stored in dir$ to OpenAI Gym Server. Uses default \ server URL. GymUpload[dir$, url$] uploads given a user-defined server url$. | 'AlgorithmID' | '' | Name of algorithm | | 'APIKey' | Automatic | When Automatic, tries to obtain key using GetEnvironment. \ otherwise, need to specify the key. | "] Options[GymUpload] = { "AlgorithmID" -> "", "APIKey" -> Automatic }; GymUpload[dir_String, url_String, opts:OptionsPattern[]] := Catch @ Module[ {data, apiKey = OptionValue["APIKey"]}, apiKey = If[apiKey === Automatic, Values @ GetEnvironment["OPENAI_GYM_API_KEY"] ]; If[apiKey === None, Throw @ Failure["GymAPIKey", <|"MessageTemplate" :> "No Gym API key defined."|>] ]; data = <| "training_dir" -> If[Head[dir] === File, First[dir], dir], "algorithm_id" -> OptionValue["AlgorithmID"], "api_key" -> apiKey |>; gymPOSTRequest[url, "/v1/upload/", data]; (* don't return *) ] GymUpload[dir_String, URL[url_], opts:OptionsPattern[]] := GymUpload[dir, url, opts] GymUpload[dir_String, opts:OptionsPattern[]] := GymUpload[dir, $DefaultServer, opts] (*************************) SetUsage[" ShutdownGymServer[] requests a server shutdown at the default server URL. ShutdownGymServer[url$] requests a shutdown of a server at the address url$, \ where url$ is either a string or URL object. "] ShutdownGymServer[url_String] := Catch @ Module[{}, gymPOSTRequest[url, "/v1/shutdown/"]; ] ShutdownGymServer[URL[url_]] := ShutdownGymServer[url] ShutdownGymServer[] := ShutdownGymServer[$DefaultServer] (*----------------------------------------------------------------------------*) End[ ] EndPackage[ ]