unitypredict 0.1.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unitypredict-0.1.17/PKG-INFO +220 -0
- unitypredict-0.1.17/README.md +215 -0
- unitypredict-0.1.17/setup.cfg +4 -0
- unitypredict-0.1.17/setup.py +24 -0
- unitypredict-0.1.17/unitypredict/Models.py +104 -0
- unitypredict-0.1.17/unitypredict/Platform.py +109 -0
- unitypredict-0.1.17/unitypredict/UnityPredictLocalHost.py +447 -0
- unitypredict-0.1.17/unitypredict/__init__.py +1 -0
- unitypredict-0.1.17/unitypredict/scripts.py +5 -0
- unitypredict-0.1.17/unitypredict.egg-info/PKG-INFO +220 -0
- unitypredict-0.1.17/unitypredict.egg-info/SOURCES.txt +12 -0
- unitypredict-0.1.17/unitypredict.egg-info/dependency_links.txt +1 -0
- unitypredict-0.1.17/unitypredict.egg-info/entry_points.txt +2 -0
- unitypredict-0.1.17/unitypredict.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: unitypredict
|
|
3
|
+
Version: 0.1.17
|
|
4
|
+
Description-Content-Type: text/markdown
|
|
5
|
+
|
|
6
|
+
# UnityPredict Local App Engine Creator
|
|
7
|
+
|
|
8
|
+
## Introduction
|
|
9
|
+
|
|
10
|
+
This library allows you to create App Engines in your local system and finetune the engines before updating it to the [ModelCentral](https://modelcentral.ai) repositories.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
* You can use pip to install the ```UnityPredict``` library.
|
|
14
|
+
```bash
|
|
15
|
+
pip install UnityPredict
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
Use the following snippet to initialize the environment.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### main.py
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
|
|
26
|
+
from UnityPredict import UnityPredictHost, Models
|
|
27
|
+
import sys
|
|
28
|
+
import uuid
|
|
29
|
+
|
|
30
|
+
import time
|
|
31
|
+
|
|
32
|
+
import os
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
platformInit = UnityPredictHost()
|
|
38
|
+
|
|
39
|
+
configStat = platformInit.isConfigInitialized()
|
|
40
|
+
|
|
41
|
+
if not configStat:
|
|
42
|
+
print ("Config Initialization Unsuccessful!!")
|
|
43
|
+
sys.exit(0)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
print ("Config Initialization Successful!!")
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
* This is the snippet to initialize the AppEngine environment.
|
|
51
|
+
* Run script using the following command:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python main.py
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
* If this script is run for the **first time**. The following Output will be shown
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
|
|
61
|
+
Config file not detected, creating templated config file: YourScriptPath/config.json
|
|
62
|
+
Config Initialization Unsuccessful!!
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
* A templated config file would be generated on the same directory as that of your main script.
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"MODEL_DIR": "YourScriptPath/models",
|
|
70
|
+
"REQUEST_DIR": "YourScriptPath/requests",
|
|
71
|
+
"SAVE_CONTEXT": true,
|
|
72
|
+
"TEMP_EXEC_DIR": "YourScriptPath",
|
|
73
|
+
"UPT_API_KEY": ""
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
* Edit the JSON as per your requirements. The various keys represent:
|
|
79
|
+
|
|
80
|
+
* **TEMP_EXEC_DIR**: Directory on which you wan the AppEngine to run
|
|
81
|
+
|
|
82
|
+
* **REQUEST_DIR**: Files or Folders to be uploaded to AppEngine for using during the execution can be added under the specified **REQUEST_DIR**
|
|
83
|
+
|
|
84
|
+
* **MODEL_DIR**: Local model files/binaries to be uploaded to AppEngine for using during the execution can be added under the specified **MODEL_DIR**
|
|
85
|
+
|
|
86
|
+
* **SAVE_CONTEXT**: Retains context across multiple requests. Disable it using ```"SAVE_CONTEXT" : false```
|
|
87
|
+
|
|
88
|
+
* **UPT_API_KEY**: API Key token generated from the ModelCentral profile of the user.
|
|
89
|
+
|
|
90
|
+
* Once configured, run the following script once again to get:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
Config Initialization Successful!!
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### EntryPoint.py
|
|
97
|
+
|
|
98
|
+
* In order to run your custom AppEngine, it is necessary to create a file named ```EntryPoint.py``` which is going to contain the inference logic.
|
|
99
|
+
|
|
100
|
+
This is an Example snippet of the `EntryPoint.py`:
|
|
101
|
+
```python
|
|
102
|
+
import json
|
|
103
|
+
from UnityPredict.Platform import IPlatform, InferenceRequest, InferenceResponse, OutcomeValue, InferenceContextData
|
|
104
|
+
from typing import List, Dict, Optional
|
|
105
|
+
from collections import deque
|
|
106
|
+
import sys
|
|
107
|
+
import datetime
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def run_local_engine(request: InferenceRequest, platform: IPlatform) -> InferenceResponse:
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
platform.logMsg("Running User Code...")
|
|
116
|
+
response = InferenceResponse()
|
|
117
|
+
|
|
118
|
+
context: Dict[str, str] = {}
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
prompt = request.InputValues['InputMessage']
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Saved context across requests
|
|
125
|
+
# Use this variable to save new context in the dict format
|
|
126
|
+
# request.Context.StoredMeta is of the format: Dict[str, str]
|
|
127
|
+
context = request.Context.StoredMeta
|
|
128
|
+
|
|
129
|
+
currentExecTime = datetime.datetime.now()
|
|
130
|
+
currentExecTime = currentExecTime.strftime("%d-%m-%YT%H-%M-%S")
|
|
131
|
+
resp_message = "Echo message: {} Time:: {}".format(prompt, currentExecTime)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# platform.getRequestFile: Fetch Files specified under the "REQUEST_DIR" in config
|
|
135
|
+
with platform.getRequestFile("myDetails.txt", "r") as reqFile:
|
|
136
|
+
|
|
137
|
+
resp_message += "\n{}".format("\n".join(reqFile.readlines()))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Fill context according to your needs
|
|
141
|
+
context[currentExecTime] = resp_message
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# platform.saveRequestFile: Creates any file type Outputs
|
|
145
|
+
# These files would be present under TEMP_EXEC_DIR/execTmp/outputs_<RequestLaunchTimeStamp>__<RequestId>
|
|
146
|
+
# TEMP_EXEC_DIR: Configured under the config.json
|
|
147
|
+
# execTmp: Creates enviroment for the AppEngine under the specified TEMP_EXEC_DIR
|
|
148
|
+
with platform.saveRequestFile("final_resp_{}.txt".format(currentExecTime), "w+") as outFile:
|
|
149
|
+
|
|
150
|
+
outFile.write(resp_message)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
cost = len(prompt)/1000 * 0.03 + len(resp_message)/1000 * 0.06
|
|
154
|
+
response.AdditionalInferenceCosts = cost
|
|
155
|
+
response.Outcomes['OutputMessage'] = [OutcomeValue(value=resp_message, probability=1.0)]
|
|
156
|
+
|
|
157
|
+
# Set the updated context back to the response
|
|
158
|
+
response.Context.StoredMeta = context
|
|
159
|
+
except Exception as e:
|
|
160
|
+
response.ErrorMessages = "Entrypoint Exception Occured: {}".format(str(e))
|
|
161
|
+
|
|
162
|
+
print("Finished Running User Code...")
|
|
163
|
+
return response
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
* Some APIs for using the AppEngine environment
|
|
169
|
+
* **request.Context.StoredMeta**:
|
|
170
|
+
* Saved context across requests
|
|
171
|
+
* Use this variable to save new context in the dict format
|
|
172
|
+
* request.Context.StoredMeta is of the format: Dict[str, str]
|
|
173
|
+
|
|
174
|
+
* **platform.getRequestFile**:
|
|
175
|
+
* Fetch Files specified under the "**REQUEST_DIR**" in config
|
|
176
|
+
|
|
177
|
+
* **platform.saveRequestFile**:
|
|
178
|
+
* Creates any file type Outputs
|
|
179
|
+
* These files would be present under **TEMP_EXEC_DIR/execTmp/*outputs_RequestLaunchTimeStamp__RequestId***
|
|
180
|
+
* **TEMP_EXEC_DIR**: Configured under the config.json
|
|
181
|
+
* execTmp: Creates enviroment for the AppEngine under the specified TEMP_EXEC_DIR
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
* Go back to the `main.py` and add the following command to run your `EntryPoint.py`
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
|
|
189
|
+
# Previous Snippet for initialization
|
|
190
|
+
platformInit = UnityPredictHost()
|
|
191
|
+
|
|
192
|
+
configStat = platformInit.isConfigInitialized()
|
|
193
|
+
|
|
194
|
+
if not configStat:
|
|
195
|
+
print ("Config Initialization Unsuccessful!!")
|
|
196
|
+
sys.exit(0)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
print ("Config Initialization Successful!!")
|
|
200
|
+
|
|
201
|
+
#### New Snippet to run EntryPoint.py via the AppEngine
|
|
202
|
+
|
|
203
|
+
request = Models.AppEngineRequest(RequestId=str(uuid.uuid4()))
|
|
204
|
+
request.EngineInputData = Models.EngineInputs(InputValues={"InputMessage": "Hi, this is the message to be echoed"}, DesiredOutcomes=[])
|
|
205
|
+
|
|
206
|
+
response : Models.UnityPredictEngineResponse = platformInit.run_engine(request=request)
|
|
207
|
+
|
|
208
|
+
# Print Outputs
|
|
209
|
+
if (response.EngineOutputs != None):
|
|
210
|
+
print ("Output: {}".format(response.EngineOutputs.toJSON()))
|
|
211
|
+
|
|
212
|
+
# Print Error Messages (if any)
|
|
213
|
+
print ("Error Messages: {}".format(response.ErrorMessages))
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## Contributing
|
|
219
|
+
|
|
220
|
+
## License
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# UnityPredict Local App Engine Creator
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
This library allows you to create App Engines in your local system and finetune the engines before updating it to the [ModelCentral](https://modelcentral.ai) repositories.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
* You can use pip to install the ```UnityPredict``` library.
|
|
9
|
+
```bash
|
|
10
|
+
pip install UnityPredict
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
Use the following snippet to initialize the environment.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### main.py
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
|
|
21
|
+
from UnityPredict import UnityPredictHost, Models
|
|
22
|
+
import sys
|
|
23
|
+
import uuid
|
|
24
|
+
|
|
25
|
+
import time
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
platformInit = UnityPredictHost()
|
|
33
|
+
|
|
34
|
+
configStat = platformInit.isConfigInitialized()
|
|
35
|
+
|
|
36
|
+
if not configStat:
|
|
37
|
+
print ("Config Initialization Unsuccessful!!")
|
|
38
|
+
sys.exit(0)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
print ("Config Initialization Successful!!")
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
* This is the snippet to initialize the AppEngine environment.
|
|
46
|
+
* Run script using the following command:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
python main.py
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
* If this script is run for the **first time**. The following Output will be shown
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
|
|
56
|
+
Config file not detected, creating templated config file: YourScriptPath/config.json
|
|
57
|
+
Config Initialization Unsuccessful!!
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
* A templated config file would be generated on the same directory as that of your main script.
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"MODEL_DIR": "YourScriptPath/models",
|
|
65
|
+
"REQUEST_DIR": "YourScriptPath/requests",
|
|
66
|
+
"SAVE_CONTEXT": true,
|
|
67
|
+
"TEMP_EXEC_DIR": "YourScriptPath",
|
|
68
|
+
"UPT_API_KEY": ""
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
* Edit the JSON as per your requirements. The various keys represent:
|
|
74
|
+
|
|
75
|
+
* **TEMP_EXEC_DIR**: Directory on which you wan the AppEngine to run
|
|
76
|
+
|
|
77
|
+
* **REQUEST_DIR**: Files or Folders to be uploaded to AppEngine for using during the execution can be added under the specified **REQUEST_DIR**
|
|
78
|
+
|
|
79
|
+
* **MODEL_DIR**: Local model files/binaries to be uploaded to AppEngine for using during the execution can be added under the specified **MODEL_DIR**
|
|
80
|
+
|
|
81
|
+
* **SAVE_CONTEXT**: Retains context across multiple requests. Disable it using ```"SAVE_CONTEXT" : false```
|
|
82
|
+
|
|
83
|
+
* **UPT_API_KEY**: API Key token generated from the ModelCentral profile of the user.
|
|
84
|
+
|
|
85
|
+
* Once configured, run the following script once again to get:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
Config Initialization Successful!!
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### EntryPoint.py
|
|
92
|
+
|
|
93
|
+
* In order to run your custom AppEngine, it is necessary to create a file named ```EntryPoint.py``` which is going to contain the inference logic.
|
|
94
|
+
|
|
95
|
+
This is an Example snippet of the `EntryPoint.py`:
|
|
96
|
+
```python
|
|
97
|
+
import json
|
|
98
|
+
from UnityPredict.Platform import IPlatform, InferenceRequest, InferenceResponse, OutcomeValue, InferenceContextData
|
|
99
|
+
from typing import List, Dict, Optional
|
|
100
|
+
from collections import deque
|
|
101
|
+
import sys
|
|
102
|
+
import datetime
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def run_local_engine(request: InferenceRequest, platform: IPlatform) -> InferenceResponse:
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
platform.logMsg("Running User Code...")
|
|
111
|
+
response = InferenceResponse()
|
|
112
|
+
|
|
113
|
+
context: Dict[str, str] = {}
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
prompt = request.InputValues['InputMessage']
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Saved context across requests
|
|
120
|
+
# Use this variable to save new context in the dict format
|
|
121
|
+
# request.Context.StoredMeta is of the format: Dict[str, str]
|
|
122
|
+
context = request.Context.StoredMeta
|
|
123
|
+
|
|
124
|
+
currentExecTime = datetime.datetime.now()
|
|
125
|
+
currentExecTime = currentExecTime.strftime("%d-%m-%YT%H-%M-%S")
|
|
126
|
+
resp_message = "Echo message: {} Time:: {}".format(prompt, currentExecTime)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# platform.getRequestFile: Fetch Files specified under the "REQUEST_DIR" in config
|
|
130
|
+
with platform.getRequestFile("myDetails.txt", "r") as reqFile:
|
|
131
|
+
|
|
132
|
+
resp_message += "\n{}".format("\n".join(reqFile.readlines()))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Fill context according to your needs
|
|
136
|
+
context[currentExecTime] = resp_message
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# platform.saveRequestFile: Creates any file type Outputs
|
|
140
|
+
# These files would be present under TEMP_EXEC_DIR/execTmp/outputs_<RequestLaunchTimeStamp>__<RequestId>
|
|
141
|
+
# TEMP_EXEC_DIR: Configured under the config.json
|
|
142
|
+
# execTmp: Creates enviroment for the AppEngine under the specified TEMP_EXEC_DIR
|
|
143
|
+
with platform.saveRequestFile("final_resp_{}.txt".format(currentExecTime), "w+") as outFile:
|
|
144
|
+
|
|
145
|
+
outFile.write(resp_message)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
cost = len(prompt)/1000 * 0.03 + len(resp_message)/1000 * 0.06
|
|
149
|
+
response.AdditionalInferenceCosts = cost
|
|
150
|
+
response.Outcomes['OutputMessage'] = [OutcomeValue(value=resp_message, probability=1.0)]
|
|
151
|
+
|
|
152
|
+
# Set the updated context back to the response
|
|
153
|
+
response.Context.StoredMeta = context
|
|
154
|
+
except Exception as e:
|
|
155
|
+
response.ErrorMessages = "Entrypoint Exception Occured: {}".format(str(e))
|
|
156
|
+
|
|
157
|
+
print("Finished Running User Code...")
|
|
158
|
+
return response
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
* Some APIs for using the AppEngine environment
|
|
164
|
+
* **request.Context.StoredMeta**:
|
|
165
|
+
* Saved context across requests
|
|
166
|
+
* Use this variable to save new context in the dict format
|
|
167
|
+
* request.Context.StoredMeta is of the format: Dict[str, str]
|
|
168
|
+
|
|
169
|
+
* **platform.getRequestFile**:
|
|
170
|
+
* Fetch Files specified under the "**REQUEST_DIR**" in config
|
|
171
|
+
|
|
172
|
+
* **platform.saveRequestFile**:
|
|
173
|
+
* Creates any file type Outputs
|
|
174
|
+
* These files would be present under **TEMP_EXEC_DIR/execTmp/*outputs_RequestLaunchTimeStamp__RequestId***
|
|
175
|
+
* **TEMP_EXEC_DIR**: Configured under the config.json
|
|
176
|
+
* execTmp: Creates enviroment for the AppEngine under the specified TEMP_EXEC_DIR
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
* Go back to the `main.py` and add the following command to run your `EntryPoint.py`
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
if __name__ == "__main__":
|
|
183
|
+
|
|
184
|
+
# Previous Snippet for initialization
|
|
185
|
+
platformInit = UnityPredictHost()
|
|
186
|
+
|
|
187
|
+
configStat = platformInit.isConfigInitialized()
|
|
188
|
+
|
|
189
|
+
if not configStat:
|
|
190
|
+
print ("Config Initialization Unsuccessful!!")
|
|
191
|
+
sys.exit(0)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
print ("Config Initialization Successful!!")
|
|
195
|
+
|
|
196
|
+
#### New Snippet to run EntryPoint.py via the AppEngine
|
|
197
|
+
|
|
198
|
+
request = Models.AppEngineRequest(RequestId=str(uuid.uuid4()))
|
|
199
|
+
request.EngineInputData = Models.EngineInputs(InputValues={"InputMessage": "Hi, this is the message to be echoed"}, DesiredOutcomes=[])
|
|
200
|
+
|
|
201
|
+
response : Models.UnityPredictEngineResponse = platformInit.run_engine(request=request)
|
|
202
|
+
|
|
203
|
+
# Print Outputs
|
|
204
|
+
if (response.EngineOutputs != None):
|
|
205
|
+
print ("Output: {}".format(response.EngineOutputs.toJSON()))
|
|
206
|
+
|
|
207
|
+
# Print Error Messages (if any)
|
|
208
|
+
print ("Error Messages: {}".format(response.ErrorMessages))
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
## Contributing
|
|
214
|
+
|
|
215
|
+
## License
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
description = ""
|
|
4
|
+
with open("README.md", "r") as rdf:
|
|
5
|
+
description = rdf.read()
|
|
6
|
+
|
|
7
|
+
print ("Possible packages: {}".format(find_packages()))
|
|
8
|
+
|
|
9
|
+
setup (
|
|
10
|
+
name="unitypredict",
|
|
11
|
+
version="0.1.17",
|
|
12
|
+
packages=find_packages(),
|
|
13
|
+
install_requires=[
|
|
14
|
+
# Currently no dependencies
|
|
15
|
+
],
|
|
16
|
+
entry_points = { # this here is the magic that binds your function into a callable script
|
|
17
|
+
'console_scripts':
|
|
18
|
+
[
|
|
19
|
+
'unitypredict=unitypredict.scripts:runcmd'
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
long_description=description,
|
|
23
|
+
long_description_content_type="text/markdown"
|
|
24
|
+
)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
class EngineResults:
|
|
6
|
+
OutcomeValues: dict = {}
|
|
7
|
+
Outcomes: dict = {}
|
|
8
|
+
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.OutcomeValues = dict()
|
|
11
|
+
self.Outcomes = dict()
|
|
12
|
+
|
|
13
|
+
def toJSON(self):
|
|
14
|
+
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
|
|
15
|
+
|
|
16
|
+
class OutcomePrediction:
|
|
17
|
+
Probability = 0.0
|
|
18
|
+
Value = None
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.Probability = 0.0
|
|
22
|
+
self.Value = None
|
|
23
|
+
|
|
24
|
+
class DataTypes(str, Enum):
|
|
25
|
+
Boolean = 'Boolean'
|
|
26
|
+
Integer = 'Integer'
|
|
27
|
+
Float = 'Float'
|
|
28
|
+
String = 'String'
|
|
29
|
+
File = 'File'
|
|
30
|
+
Tensor = 'Tensor'
|
|
31
|
+
|
|
32
|
+
class InputInfo (BaseModel):
|
|
33
|
+
Name: str = ''
|
|
34
|
+
InputType: DataTypes = DataTypes.Integer
|
|
35
|
+
|
|
36
|
+
class OutcomeInfo (BaseModel):
|
|
37
|
+
Name: str = ''
|
|
38
|
+
OutcomeType: DataTypes = DataTypes.Integer
|
|
39
|
+
|
|
40
|
+
class BasePredictEngineConfig (BaseModel):
|
|
41
|
+
# Inputs: list[InputInfo] = None
|
|
42
|
+
# Outcomes: list[OutcomeInfo] = None
|
|
43
|
+
Inputs: list
|
|
44
|
+
Outcomes: list
|
|
45
|
+
|
|
46
|
+
class InferenceContext (BaseModel):
|
|
47
|
+
ContextId: str = ''
|
|
48
|
+
StoredMeta: dict = {}
|
|
49
|
+
|
|
50
|
+
def toJSON(self):
|
|
51
|
+
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
|
|
52
|
+
|
|
53
|
+
class EngineInputs (BaseModel):
|
|
54
|
+
InputValues: dict
|
|
55
|
+
DesiredOutcomes: list
|
|
56
|
+
|
|
57
|
+
# Note: these models will be different for every engine type
|
|
58
|
+
###############################################################################################################
|
|
59
|
+
class AppEngineInferenceOptions (BaseModel):
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
class AIEngineConfiguration (BasePredictEngineConfig):
|
|
63
|
+
InferenceOptions: AppEngineInferenceOptions = None
|
|
64
|
+
|
|
65
|
+
class AppEngineRequest (BaseModel):
|
|
66
|
+
RequestId: str = ''
|
|
67
|
+
EngineId: str = ''
|
|
68
|
+
RequestInputFiles: bool = False
|
|
69
|
+
RequestOutputFiles: bool = False
|
|
70
|
+
RequestFilesFolderPath: str = False
|
|
71
|
+
PackagesFolderPath: str = ''
|
|
72
|
+
PackagesFolderPath: str = ''
|
|
73
|
+
SourcesFolderPath: str = ''
|
|
74
|
+
ModelFilesFolderPath: str = ''
|
|
75
|
+
EngineApiKey: str = ''
|
|
76
|
+
PredictEndpoint: str = ''
|
|
77
|
+
Context: InferenceContext = None
|
|
78
|
+
CallbackQueue: str = ''
|
|
79
|
+
EngineInputData: EngineInputs = None
|
|
80
|
+
EngineConfig: AIEngineConfiguration = None
|
|
81
|
+
|
|
82
|
+
def toJSON(self):
|
|
83
|
+
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
|
|
84
|
+
|
|
85
|
+
###############################################################################################################
|
|
86
|
+
|
|
87
|
+
class UnityPredictEngineResponse:
|
|
88
|
+
RequestId: str = ''
|
|
89
|
+
ErrorMessages: str = ''
|
|
90
|
+
LogMessages: str = ''
|
|
91
|
+
AdditionalInferenceCosts: float = 0.0
|
|
92
|
+
EngineOutputs: EngineResults = None
|
|
93
|
+
Context: InferenceContext = None
|
|
94
|
+
|
|
95
|
+
def __init__(self):
|
|
96
|
+
self.RequestId = ''
|
|
97
|
+
self.ErrorMessages = ''
|
|
98
|
+
self.LogMessages = ''
|
|
99
|
+
self.AdditionalInferenceCosts = 0.0
|
|
100
|
+
self.EngineOutputs = None
|
|
101
|
+
self.Context = None
|
|
102
|
+
|
|
103
|
+
def toJSON(self):
|
|
104
|
+
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from abc import ABCMeta, abstractmethod
|
|
2
|
+
from io import BufferedReader, IOBase
|
|
3
|
+
|
|
4
|
+
class OutcomeValue:
|
|
5
|
+
Probability = 0.0
|
|
6
|
+
Value = None
|
|
7
|
+
|
|
8
|
+
def __init__(self, value: any = '', probability: float = 0.0):
|
|
9
|
+
self.Probability = probability
|
|
10
|
+
self.Value = value
|
|
11
|
+
|
|
12
|
+
class InferenceContextData:
|
|
13
|
+
StoredMeta: dict = {}
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self.StoredMeta = {}
|
|
17
|
+
|
|
18
|
+
class InferenceRequest:
|
|
19
|
+
InputValues: dict
|
|
20
|
+
DesiredOutcomes: list
|
|
21
|
+
Context: InferenceContextData = None
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.Context = InferenceContextData()
|
|
24
|
+
self.InputValues = {}
|
|
25
|
+
self.DesiredOutcomes = []
|
|
26
|
+
|
|
27
|
+
class InferenceResponse:
|
|
28
|
+
ErrorMessages: str = ''
|
|
29
|
+
AdditionalInferenceCosts: float = 0.0
|
|
30
|
+
Context: InferenceContextData = None
|
|
31
|
+
OutcomeValues: dict = {}
|
|
32
|
+
Outcomes: dict = {}
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
self.ErrorMessages = ''
|
|
36
|
+
self.AdditionalInferenceCosts = 0.0
|
|
37
|
+
self.Context = InferenceContextData()
|
|
38
|
+
self.OutcomeValues = {}
|
|
39
|
+
self.Outcomes = {}
|
|
40
|
+
|
|
41
|
+
class ChainedInferenceRequest:
|
|
42
|
+
ContextId: str = ''
|
|
43
|
+
InputValues: dict
|
|
44
|
+
DesiredOutcomes: list
|
|
45
|
+
|
|
46
|
+
def __init__(self):
|
|
47
|
+
self.ContextId = ''
|
|
48
|
+
self.InputValues = {}
|
|
49
|
+
self.DesiredOutcomes = []
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ChainedInferenceResponse:
|
|
53
|
+
ContextId: str = ''
|
|
54
|
+
RequestId: str = ''
|
|
55
|
+
ErrorMessages: str = ''
|
|
56
|
+
ComputeCost: float = 0.0
|
|
57
|
+
OutcomeValues: dict = {}
|
|
58
|
+
Outcomes: dict = {}
|
|
59
|
+
|
|
60
|
+
def __init__(self):
|
|
61
|
+
self.ContextId = ''
|
|
62
|
+
self.RequestId = ''
|
|
63
|
+
self.ErrorMessages = ''
|
|
64
|
+
self.ComputeCost = 0.0
|
|
65
|
+
self.OutcomeValues = {}
|
|
66
|
+
self.Outcomes = {}
|
|
67
|
+
|
|
68
|
+
class FileTransmissionObj:
|
|
69
|
+
FileName: str = ''
|
|
70
|
+
FileHandle: IOBase = None
|
|
71
|
+
|
|
72
|
+
def __init__(self, fileName, fileHandle):
|
|
73
|
+
self.FileName = fileName
|
|
74
|
+
self.FileHandle = fileHandle
|
|
75
|
+
|
|
76
|
+
class FileReceivedObj:
|
|
77
|
+
FileName: str = ''
|
|
78
|
+
LocalFilePath: str = ''
|
|
79
|
+
|
|
80
|
+
def __init__(self, fileName, localFilePath):
|
|
81
|
+
self.FileName = fileName
|
|
82
|
+
self.LocalFilePath = localFilePath
|
|
83
|
+
|
|
84
|
+
class IPlatform:
|
|
85
|
+
__metaclass__ = ABCMeta
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def version(self): return "1.0"
|
|
89
|
+
|
|
90
|
+
@abstractmethod
|
|
91
|
+
def getModelsFolderPath(self) -> str: raise NotImplementedError
|
|
92
|
+
|
|
93
|
+
@abstractmethod
|
|
94
|
+
def getModelFile(self, modelFileName: str, mode: str = 'rb') -> IOBase: raise NotImplementedError
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def getRequestFile(self, modelFileName: str, mode: str = 'rb') -> IOBase: raise NotImplementedError
|
|
98
|
+
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def saveRequestFile(self, modelFileName: str, mode: str = 'wb') -> IOBase: raise NotImplementedError
|
|
101
|
+
|
|
102
|
+
@abstractmethod
|
|
103
|
+
def getLocalTempFolderPath(self) -> str: raise NotImplementedError
|
|
104
|
+
|
|
105
|
+
@abstractmethod
|
|
106
|
+
def logMsg(self, msg: str): raise NotImplementedError
|
|
107
|
+
|
|
108
|
+
@abstractmethod
|
|
109
|
+
def invokeUnityPredictModel(self, modelId: str, request: ChainedInferenceRequest) -> ChainedInferenceResponse: raise NotImplementedError
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from io import BufferedReader, IOBase, StringIO
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
import json
|
|
6
|
+
import os, sys
|
|
7
|
+
import shutil
|
|
8
|
+
import datetime
|
|
9
|
+
import uuid
|
|
10
|
+
import filecmp
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
from .Models import AppEngineRequest, EngineResults, InferenceContext, UnityPredictEngineResponse
|
|
14
|
+
from .Platform import ChainedInferenceRequest, ChainedInferenceResponse, FileReceivedObj, FileTransmissionObj, IPlatform, InferenceRequest,InferenceResponse, InferenceContextData
|
|
15
|
+
|
|
16
|
+
class AppEngineConfig (BaseModel):
|
|
17
|
+
TEMP_EXEC_DIR: str = os.getcwd()
|
|
18
|
+
UPT_API_KEY: str = ""
|
|
19
|
+
MODEL_DIR: str = os.path.join(os.getcwd(), "models")
|
|
20
|
+
REQUEST_DIR: str = os.path.join(os.getcwd(), "requests")
|
|
21
|
+
SAVE_CONTEXT: bool = True
|
|
22
|
+
|
|
23
|
+
def toJSON(self):
|
|
24
|
+
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)
|
|
25
|
+
|
|
26
|
+
class UnityPredictHost(IPlatform):
|
|
27
|
+
|
|
28
|
+
Initialized = False
|
|
29
|
+
isConfigInit = False
|
|
30
|
+
LoadedEngineId = None
|
|
31
|
+
CurrentRequest: AppEngineRequest = None
|
|
32
|
+
logMsgs: str = ''
|
|
33
|
+
|
|
34
|
+
configFile = os.path.join(os.getcwd(), "config.json")
|
|
35
|
+
appEngineConfig: AppEngineConfig = AppEngineConfig()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
execTempDir = "execTmp"
|
|
39
|
+
execRequestFolder: str = "requests"
|
|
40
|
+
execModelFolder: str = "models"
|
|
41
|
+
execOutputFolder: str = "outputs"
|
|
42
|
+
|
|
43
|
+
def __init__(self) -> None:
|
|
44
|
+
|
|
45
|
+
config_dict : dict = {}
|
|
46
|
+
if not os.path.exists(self.configFile):
|
|
47
|
+
|
|
48
|
+
print ("Config file not detected, creating templated config file: {}".format(self.configFile))
|
|
49
|
+
|
|
50
|
+
self.appEngineConfig.TEMP_EXEC_DIR = os.getcwd()
|
|
51
|
+
|
|
52
|
+
with open(self.configFile, "w+") as confFile:
|
|
53
|
+
confFile.write(self.appEngineConfig.toJSON())
|
|
54
|
+
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
print ("Config file detected, loading data from: {}".format(self.configFile))
|
|
58
|
+
with open (self.configFile, "r+") as confFile:
|
|
59
|
+
config_dict = json.load(confFile)
|
|
60
|
+
|
|
61
|
+
self.appEngineConfig = AppEngineConfig(**config_dict)
|
|
62
|
+
self.CurrentRequest = AppEngineRequest()
|
|
63
|
+
|
|
64
|
+
self.workingDir = os.path.join(self.appEngineConfig.TEMP_EXEC_DIR, self.execTempDir)
|
|
65
|
+
self.CurrentRequest.EngineApiKey = self.appEngineConfig.UPT_API_KEY
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
self.CurrentRequest.ModelFilesFolderPath = os.path.join(self.workingDir, self.execModelFolder)
|
|
69
|
+
self.CurrentRequest.RequestFilesFolderPath = os.path.join(self.workingDir, self.execRequestFolder)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
self.isConfigInit = True
|
|
73
|
+
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
def isConfigInitialized(self) -> bool:
|
|
77
|
+
|
|
78
|
+
return self.isConfigInit
|
|
79
|
+
|
|
80
|
+
def isPlatformInitialized(self) -> bool:
|
|
81
|
+
|
|
82
|
+
return self.Initialized
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def getModelsFolderPath(self) -> str:
|
|
86
|
+
# ToDo: return the correct path
|
|
87
|
+
return self.CurrentRequest.ModelFilesFolderPath
|
|
88
|
+
|
|
89
|
+
def getModelFile(self, modelFileName: str, mode: str = 'rb') -> IOBase:
|
|
90
|
+
# ToDo: return the correct file handle
|
|
91
|
+
absFilePath = os.path.join(self.getModelsFolderPath(), modelFileName)
|
|
92
|
+
fileHandler = open(absFilePath, mode)
|
|
93
|
+
|
|
94
|
+
return fileHandler
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def getLocalTempFolderPath(self) -> str:
|
|
98
|
+
# ToDo: return the correct path
|
|
99
|
+
return self.workingDir
|
|
100
|
+
|
|
101
|
+
def getRequestFolderPath(self) -> str:
|
|
102
|
+
# ToDo: return the correct path
|
|
103
|
+
return self.CurrentRequest.RequestFilesFolderPath
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def getRequestFile(self, requestFileName: str, mode: str = 'rb') -> IOBase:
|
|
107
|
+
# ToDo: return the correct file handle
|
|
108
|
+
absFilePath = os.path.join(self.getRequestFolderPath(), requestFileName)
|
|
109
|
+
fileHandler = open(absFilePath, mode)
|
|
110
|
+
|
|
111
|
+
return fileHandler
|
|
112
|
+
|
|
113
|
+
def saveRequestFile(self, requestFileName: str, mode: str = 'wb') -> IOBase:
|
|
114
|
+
# ToDo: return the correct file handle
|
|
115
|
+
absFilePath = os.path.join(self.execOutputFolder, requestFileName)
|
|
116
|
+
fileHandler = open(absFilePath, mode)
|
|
117
|
+
|
|
118
|
+
return fileHandler
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def logMsg(self, msg: str):
|
|
122
|
+
self.logMsgs += "\n{}\n".format(msg)
|
|
123
|
+
|
|
124
|
+
def errorMsg(self, msg: str):
|
|
125
|
+
self.errorMsgs += "\n{}\n".format(msg)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def syncDirectories(self, source: str, destination: str, create_dest_if_not_present: bool = False):
|
|
129
|
+
|
|
130
|
+
if not os.path.exists(source) or not os.path.isdir(source):
|
|
131
|
+
print(f"Source directory '{source}' does not exist or is not a directory.")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
if create_dest_if_not_present:
|
|
135
|
+
if not os.path.exists(destination):
|
|
136
|
+
os.makedirs(destination)
|
|
137
|
+
else:
|
|
138
|
+
if not os.path.exists(destination) or not os.path.isdir(source):
|
|
139
|
+
print(f"Destination directory '{destination}' does not exist or is not a directory.")
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
# Copy or update files from source to destination
|
|
143
|
+
for src_dir, _, files in os.walk(source):
|
|
144
|
+
dst_dir = src_dir.replace(source, destination, 1)
|
|
145
|
+
if not os.path.exists(dst_dir):
|
|
146
|
+
os.makedirs(dst_dir)
|
|
147
|
+
|
|
148
|
+
for file in files:
|
|
149
|
+
src_file = os.path.join(src_dir, file)
|
|
150
|
+
dst_file = os.path.join(dst_dir, file)
|
|
151
|
+
|
|
152
|
+
if not os.path.exists(dst_file) or not filecmp.cmp(src_file, dst_file, shallow=False):
|
|
153
|
+
shutil.copy2(src_file, dst_file) # Preserve metadata with copy2
|
|
154
|
+
print(f"Copied: {src_file} to {dst_file}")
|
|
155
|
+
else:
|
|
156
|
+
print(f"Skipped: {src_file} is already up to date.")
|
|
157
|
+
|
|
158
|
+
# Remove files and directories from destination that are not in source
|
|
159
|
+
for dst_dir, _, files in os.walk(destination, topdown=False):
|
|
160
|
+
src_dir = dst_dir.replace(destination, source, 1)
|
|
161
|
+
|
|
162
|
+
if not os.path.exists(src_dir):
|
|
163
|
+
shutil.rmtree(dst_dir)
|
|
164
|
+
print(f"Removed directory: {dst_dir}")
|
|
165
|
+
else:
|
|
166
|
+
for file in files:
|
|
167
|
+
dst_file = os.path.join(dst_dir, file)
|
|
168
|
+
src_file = os.path.join(src_dir, file)
|
|
169
|
+
|
|
170
|
+
if not os.path.exists(src_file):
|
|
171
|
+
os.remove(dst_file)
|
|
172
|
+
print(f"Removed file: {dst_file}")
|
|
173
|
+
|
|
174
|
+
def createPlatform(self, request: AppEngineRequest) -> bool:
|
|
175
|
+
if not self.isConfigInit:
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
# Creating output folder based on RequestID
|
|
179
|
+
if (request.RequestId == ""):
|
|
180
|
+
|
|
181
|
+
self.errorMsg("Please provide a proper request id\n")
|
|
182
|
+
|
|
183
|
+
return False
|
|
184
|
+
|
|
185
|
+
# Create the folders
|
|
186
|
+
if not os.path.exists(self.workingDir):
|
|
187
|
+
os.mkdir(self.workingDir)
|
|
188
|
+
|
|
189
|
+
if not os.path.exists(self.CurrentRequest.ModelFilesFolderPath):
|
|
190
|
+
os.mkdir(self.CurrentRequest.ModelFilesFolderPath)
|
|
191
|
+
|
|
192
|
+
# Create request folder having the name of the current Timestamp
|
|
193
|
+
currentExecTime = datetime.datetime.now()
|
|
194
|
+
currentExecTime = currentExecTime.strftime("%d-%m-%YT%H-%M-%S")
|
|
195
|
+
|
|
196
|
+
self.execRequestFolder = "{}_{}__{}".format(self.execRequestFolder, currentExecTime, request.RequestId)
|
|
197
|
+
self.CurrentRequest.RequestFilesFolderPath = os.path.join(self.workingDir, self.execRequestFolder)
|
|
198
|
+
|
|
199
|
+
if not os.path.exists(self.CurrentRequest.RequestFilesFolderPath):
|
|
200
|
+
os.mkdir(self.CurrentRequest.RequestFilesFolderPath)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# Create output folder based on the request Id
|
|
204
|
+
self.execOutputFolder = "{}_{}__{}".format(self.execOutputFolder, currentExecTime, request.RequestId)
|
|
205
|
+
self.execOutputFolder = os.path.join(self.workingDir, self.execOutputFolder)
|
|
206
|
+
if not os.path.exists(self.execOutputFolder):
|
|
207
|
+
os.mkdir(self.execOutputFolder)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
# TODO: Copy user configured folder contents to respective exec folders
|
|
211
|
+
# Sync Request Files
|
|
212
|
+
self.syncDirectories(self.appEngineConfig.REQUEST_DIR, self.CurrentRequest.RequestFilesFolderPath)
|
|
213
|
+
# Sync Model Files
|
|
214
|
+
self.syncDirectories(self.appEngineConfig.MODEL_DIR, self.CurrentRequest.ModelFilesFolderPath)
|
|
215
|
+
|
|
216
|
+
self.Initialized = True
|
|
217
|
+
|
|
218
|
+
return True
|
|
219
|
+
|
|
220
|
+
def run_engine(self, request: AppEngineRequest) -> UnityPredictEngineResponse:
|
|
221
|
+
|
|
222
|
+
self.logMsgs: str = ''
|
|
223
|
+
self.MaxlogMsgBuffer: int = 3000
|
|
224
|
+
self.NegMaxlogMsgBuffer: int = -1 * self.MaxlogMsgBuffer
|
|
225
|
+
|
|
226
|
+
self.errorMsgs: str = ''
|
|
227
|
+
self.MaxErrorMsgsBuffer: int = 3000
|
|
228
|
+
self.NegMaxErrorMsgsBuffer: int = -1 * self.MaxErrorMsgsBuffer
|
|
229
|
+
|
|
230
|
+
toreturn: UnityPredictEngineResponse = UnityPredictEngineResponse()
|
|
231
|
+
try:
|
|
232
|
+
|
|
233
|
+
# Create the platform for execution
|
|
234
|
+
# 1) Prepare temp folders on local directories for A) Request Files B) Model Files C) Temp Files
|
|
235
|
+
# 2) Put the files into the right folders
|
|
236
|
+
if not self.createPlatform(request=request):
|
|
237
|
+
|
|
238
|
+
self.errorMsg("Unable to create the platform")
|
|
239
|
+
toreturn.ErrorMessages = self.errorMsgs[self.NegMaxErrorMsgsBuffer:] # limit length of the logs
|
|
240
|
+
toreturn.LogMessages = self.logMsgs[self.NegMaxlogMsgBuffer:] # limit length of the logs
|
|
241
|
+
|
|
242
|
+
return toreturn
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# 3) Store/Restore Context (probably using some local json file) if requested by user
|
|
246
|
+
# Initialize context
|
|
247
|
+
|
|
248
|
+
if (self.appEngineConfig.SAVE_CONTEXT):
|
|
249
|
+
|
|
250
|
+
contextJson = os.path.join(self.getLocalTempFolderPath(), "context.json")
|
|
251
|
+
|
|
252
|
+
context = {}
|
|
253
|
+
if os.path.exists(contextJson):
|
|
254
|
+
with open(contextJson, "r+") as ctxtf:
|
|
255
|
+
context = json.load(ctxtf)
|
|
256
|
+
|
|
257
|
+
request.Context = InferenceContext(**context)
|
|
258
|
+
|
|
259
|
+
# 4) Convert the AppEngineRequest to the InferenceRequest that the model needs
|
|
260
|
+
|
|
261
|
+
inferReq: InferenceRequest = InferenceRequest()
|
|
262
|
+
inferReq.InputValues = request.EngineInputData.InputValues
|
|
263
|
+
inferReq.DesiredOutcomes = request.EngineInputData.DesiredOutcomes
|
|
264
|
+
|
|
265
|
+
inferReq.Context = InferenceContextData()
|
|
266
|
+
|
|
267
|
+
if (request.Context != None):
|
|
268
|
+
inferReq.Context.StoredMeta = request.Context.StoredMeta
|
|
269
|
+
else:
|
|
270
|
+
inferReq.Context.StoredMeta = {}
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# 5) Run EntryPoint.py
|
|
274
|
+
entryPoint = importlib.import_module("EntryPoint")
|
|
275
|
+
inferResp: InferenceResponse = entryPoint.run_local_engine(inferReq, self)
|
|
276
|
+
|
|
277
|
+
# 6) Copy InferenceResponse to UnityPredictEngineResponse
|
|
278
|
+
|
|
279
|
+
toreturn.AdditionalInferenceCosts = inferResp.AdditionalInferenceCosts
|
|
280
|
+
toreturn.EngineOutputs = EngineResults()
|
|
281
|
+
|
|
282
|
+
if (inferResp.Outcomes != None):
|
|
283
|
+
toreturn.EngineOutputs.Outcomes = inferResp.Outcomes
|
|
284
|
+
else:
|
|
285
|
+
toreturn.EngineOutputs.Outcomes = {}
|
|
286
|
+
|
|
287
|
+
if (inferResp.OutcomeValues != None):
|
|
288
|
+
toreturn.EngineOutputs.OutcomeValues = inferResp.OutcomeValues
|
|
289
|
+
else:
|
|
290
|
+
toreturn.EngineOutputs.OutcomeValues = {}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
toreturn.EngineOutputs.OutcomeValues = inferResp.OutcomeValues
|
|
294
|
+
toreturn.ErrorMessages = inferResp.ErrorMessages
|
|
295
|
+
|
|
296
|
+
if (self.appEngineConfig.SAVE_CONTEXT):
|
|
297
|
+
|
|
298
|
+
toreturn.Context = InferenceContext(ContextId="")
|
|
299
|
+
if (request.Context != None):
|
|
300
|
+
toreturn.Context.ContextId = request.Context.ContextId
|
|
301
|
+
if (inferResp.Context == None or inferResp.Context.StoredMeta == None or inferResp.Context.StoredMeta == {}):
|
|
302
|
+
|
|
303
|
+
if (request.Context != None):
|
|
304
|
+
toreturn.Context.StoredMeta = inferReq.Context.StoredMeta
|
|
305
|
+
else:
|
|
306
|
+
toreturn.Context.StoredMeta = {}
|
|
307
|
+
|
|
308
|
+
else:
|
|
309
|
+
toreturn.Context.StoredMeta = inferResp.Context.StoredMeta
|
|
310
|
+
|
|
311
|
+
if toreturn.Context.ContextId == "":
|
|
312
|
+
toreturn.Context.ContextId = str(uuid.uuid4())
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
with open(contextJson, "w+") as ctxtf:
|
|
316
|
+
ctxtf.write(toreturn.Context.toJSON())
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
toreturn.LogMessages = self.logMsgs[self.NegMaxlogMsgBuffer:] # limit length of the logs
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
|
|
323
|
+
print ("Error occured: {}".format(str(e)))
|
|
324
|
+
self.errorMsg(str(e))
|
|
325
|
+
toreturn.ErrorMessages += self.errorMsgs[self.NegMaxErrorMsgsBuffer:] # limit length of the logs
|
|
326
|
+
toreturn.LogMessages = self.logMsgs[self.NegMaxlogMsgBuffer:] # limit length of the logs
|
|
327
|
+
|
|
328
|
+
return toreturn
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def invokeUnityPredictModel(self, modelId: str, request: ChainedInferenceRequest) -> ChainedInferenceResponse:
|
|
332
|
+
results = ChainedInferenceResponse()
|
|
333
|
+
|
|
334
|
+
apiKey = self.CurrentRequest.EngineApiKey
|
|
335
|
+
apiBaseUrl = self.CurrentRequest.PredictEndpoint.rstrip('/')
|
|
336
|
+
|
|
337
|
+
needFileUpload: bool = False
|
|
338
|
+
|
|
339
|
+
#####
|
|
340
|
+
# The request can contain file objects so we need to change those to file names before sending out
|
|
341
|
+
#####
|
|
342
|
+
# first get a list of file that we'll need to upload later & update the POST obj
|
|
343
|
+
filesToUpload = {}
|
|
344
|
+
for xvarName in request.InputValues:
|
|
345
|
+
if isinstance(request.InputValues.get(xvarName), FileTransmissionObj):
|
|
346
|
+
needFileUpload = True
|
|
347
|
+
break
|
|
348
|
+
|
|
349
|
+
finalResponseJson: any = ''
|
|
350
|
+
|
|
351
|
+
response: requests.Response = None
|
|
352
|
+
if not needFileUpload:
|
|
353
|
+
# serialize the POST obj
|
|
354
|
+
jsonBody = json.dumps(request, default=vars)
|
|
355
|
+
|
|
356
|
+
# there are no files to upload so just post normally
|
|
357
|
+
response = requests.post(url = "{}/{}".format(apiBaseUrl, modelId), data=jsonBody, headers={"Authorization": "Bearer {}".format(apiKey)})
|
|
358
|
+
|
|
359
|
+
if response.status_code != 200:
|
|
360
|
+
results.ErrorMessages = 'Error from server: {}'.format(response.status_code)
|
|
361
|
+
return results
|
|
362
|
+
|
|
363
|
+
finalResponseJson = response.json()
|
|
364
|
+
else:
|
|
365
|
+
# we need to initialize first
|
|
366
|
+
response = requests.post(url = "{}/initialize/{}".format(apiBaseUrl, modelId), data="", headers={"Authorization": "Bearer {}".format(apiKey)})
|
|
367
|
+
|
|
368
|
+
requestId: str = response.json().get('requestId')
|
|
369
|
+
|
|
370
|
+
if response.status_code != 200:
|
|
371
|
+
results.ErrorMessages = 'Error from server: {}'.format(response.status_code)
|
|
372
|
+
return results
|
|
373
|
+
|
|
374
|
+
# upload the files
|
|
375
|
+
for xvarName in request.InputValues:
|
|
376
|
+
if isinstance(request.InputValues.get(xvarName), FileTransmissionObj):
|
|
377
|
+
fileToUpload: FileTransmissionObj = request.InputValues.get(xvarName)
|
|
378
|
+
response = requests.get(url = "{}/upload/{}/{}".format(apiBaseUrl, requestId, fileToUpload.FileName), headers={"Authorization": "Bearer {}".format(apiKey)})
|
|
379
|
+
uploadLink = response.json().get('uploadLink')
|
|
380
|
+
fileName = response.json().get('fileName')
|
|
381
|
+
request.InputValues[xvarName] = fileName # make sure that only the filename is in the request that we are going to POST
|
|
382
|
+
requests.put(url = uploadLink, data=fileToUpload.FileHandle)
|
|
383
|
+
|
|
384
|
+
jsonBody = json.dumps(request, default=vars)
|
|
385
|
+
|
|
386
|
+
response = requests.post(url = "{}/{}/{}".format(apiBaseUrl, modelId, requestId), data=jsonBody, headers={"Authorization": "Bearer {}".format(apiKey)})
|
|
387
|
+
|
|
388
|
+
finalResponseJson = response.json()
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
print(finalResponseJson)
|
|
392
|
+
|
|
393
|
+
while (finalResponseJson.get('status') == 'Processing'):
|
|
394
|
+
statusUrl: str = finalResponseJson.get('statusUrl')
|
|
395
|
+
|
|
396
|
+
response = requests.get(url = statusUrl, headers={"Authorization": "Bearer {}".format(apiKey)})
|
|
397
|
+
finalResponseJson = response.json()
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
finalResponseRequestId: str = finalResponseJson.get('requestId')
|
|
401
|
+
|
|
402
|
+
tempOutputFolder: str = os.path.join(self.getLocalTempFolderPath(), "chainedResults", finalResponseRequestId)
|
|
403
|
+
if not os.path.exists(tempOutputFolder):
|
|
404
|
+
os.makedirs(tempOutputFolder)
|
|
405
|
+
|
|
406
|
+
outcomeValues = finalResponseJson.get('outcomeValues')
|
|
407
|
+
for outputVarName in outcomeValues:
|
|
408
|
+
outcome: dict = outcomeValues.get(outputVarName)
|
|
409
|
+
if outcome.get('dataType') == 'File':
|
|
410
|
+
fileName = outcome.get('value')
|
|
411
|
+
|
|
412
|
+
tempFilePath = os.path.join(tempOutputFolder, fileName)
|
|
413
|
+
response = requests.get(url = "{}/download/{}/{}".format(apiBaseUrl, finalResponseRequestId, fileName), headers={"Authorization": "Bearer {}".format(apiKey)})
|
|
414
|
+
with open(tempFilePath, 'wb') as f:
|
|
415
|
+
f.write(response.content)
|
|
416
|
+
|
|
417
|
+
fileReceived: FileReceivedObj = FileReceivedObj(fileName, tempFilePath)
|
|
418
|
+
outcome['value'] = fileReceived
|
|
419
|
+
|
|
420
|
+
outcomes = finalResponseJson.get('outcomes')
|
|
421
|
+
for outputVarName in outcomes:
|
|
422
|
+
outcome: list = outcomes.get(outputVarName)
|
|
423
|
+
for outcomeItem in outcome:
|
|
424
|
+
if outcomeItem.get('dataType') == 'File':
|
|
425
|
+
fileName = outcomeItem.get('value')
|
|
426
|
+
|
|
427
|
+
tempFilePath = os.path.join(tempOutputFolder, fileName)
|
|
428
|
+
response = requests.get(url = "{}/download/{}/{}".format(apiBaseUrl, finalResponseRequestId, fileName), headers={"Authorization": "Bearer {}".format(apiKey)})
|
|
429
|
+
with open(tempFilePath, 'wb') as f:
|
|
430
|
+
f.write(response.content)
|
|
431
|
+
|
|
432
|
+
fileReceived: FileReceivedObj = FileReceivedObj(fileName, tempFilePath)
|
|
433
|
+
outcomeItem['value'] = fileReceived
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
results.ComputeCost = finalResponseJson.get('computeCost')
|
|
438
|
+
results.OutcomeValues = outcomeValues
|
|
439
|
+
results.Outcomes = outcomes
|
|
440
|
+
results.RequestId = finalResponseRequestId
|
|
441
|
+
results.ContextId = finalResponseJson.get('contextId')
|
|
442
|
+
results.ErrorMessages = finalResponseJson.get('errorMessages')
|
|
443
|
+
|
|
444
|
+
except Exception as e:
|
|
445
|
+
print(e)
|
|
446
|
+
|
|
447
|
+
return results
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .UnityPredictLocalHost import UnityPredictHost
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: unitypredict
|
|
3
|
+
Version: 0.1.17
|
|
4
|
+
Description-Content-Type: text/markdown
|
|
5
|
+
|
|
6
|
+
# UnityPredict Local App Engine Creator
|
|
7
|
+
|
|
8
|
+
## Introduction
|
|
9
|
+
|
|
10
|
+
This library allows you to create App Engines in your local system and finetune the engines before updating it to the [ModelCentral](https://modelcentral.ai) repositories.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
* You can use pip to install the ```UnityPredict``` library.
|
|
14
|
+
```bash
|
|
15
|
+
pip install UnityPredict
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
Use the following snippet to initialize the environment.
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### main.py
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
|
|
26
|
+
from UnityPredict import UnityPredictHost, Models
|
|
27
|
+
import sys
|
|
28
|
+
import uuid
|
|
29
|
+
|
|
30
|
+
import time
|
|
31
|
+
|
|
32
|
+
import os
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
platformInit = UnityPredictHost()
|
|
38
|
+
|
|
39
|
+
configStat = platformInit.isConfigInitialized()
|
|
40
|
+
|
|
41
|
+
if not configStat:
|
|
42
|
+
print ("Config Initialization Unsuccessful!!")
|
|
43
|
+
sys.exit(0)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
print ("Config Initialization Successful!!")
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
* This is the snippet to initialize the AppEngine environment.
|
|
51
|
+
* Run script using the following command:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python main.py
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
* If this script is run for the **first time**. The following Output will be shown
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
|
|
61
|
+
Config file not detected, creating templated config file: YourScriptPath/config.json
|
|
62
|
+
Config Initialization Unsuccessful!!
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
* A templated config file would be generated on the same directory as that of your main script.
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"MODEL_DIR": "YourScriptPath/models",
|
|
70
|
+
"REQUEST_DIR": "YourScriptPath/requests",
|
|
71
|
+
"SAVE_CONTEXT": true,
|
|
72
|
+
"TEMP_EXEC_DIR": "YourScriptPath",
|
|
73
|
+
"UPT_API_KEY": ""
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
* Edit the JSON as per your requirements. The various keys represent:
|
|
79
|
+
|
|
80
|
+
* **TEMP_EXEC_DIR**: Directory on which you wan the AppEngine to run
|
|
81
|
+
|
|
82
|
+
* **REQUEST_DIR**: Files or Folders to be uploaded to AppEngine for using during the execution can be added under the specified **REQUEST_DIR**
|
|
83
|
+
|
|
84
|
+
* **MODEL_DIR**: Local model files/binaries to be uploaded to AppEngine for using during the execution can be added under the specified **MODEL_DIR**
|
|
85
|
+
|
|
86
|
+
* **SAVE_CONTEXT**: Retains context across multiple requests. Disable it using ```"SAVE_CONTEXT" : false```
|
|
87
|
+
|
|
88
|
+
* **UPT_API_KEY**: API Key token generated from the ModelCentral profile of the user.
|
|
89
|
+
|
|
90
|
+
* Once configured, run the following script once again to get:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
Config Initialization Successful!!
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### EntryPoint.py
|
|
97
|
+
|
|
98
|
+
* In order to run your custom AppEngine, it is necessary to create a file named ```EntryPoint.py``` which is going to contain the inference logic.
|
|
99
|
+
|
|
100
|
+
This is an Example snippet of the `EntryPoint.py`:
|
|
101
|
+
```python
|
|
102
|
+
import json
|
|
103
|
+
from UnityPredict.Platform import IPlatform, InferenceRequest, InferenceResponse, OutcomeValue, InferenceContextData
|
|
104
|
+
from typing import List, Dict, Optional
|
|
105
|
+
from collections import deque
|
|
106
|
+
import sys
|
|
107
|
+
import datetime
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def run_local_engine(request: InferenceRequest, platform: IPlatform) -> InferenceResponse:
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
platform.logMsg("Running User Code...")
|
|
116
|
+
response = InferenceResponse()
|
|
117
|
+
|
|
118
|
+
context: Dict[str, str] = {}
|
|
119
|
+
|
|
120
|
+
try:
|
|
121
|
+
prompt = request.InputValues['InputMessage']
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
# Saved context across requests
|
|
125
|
+
# Use this variable to save new context in the dict format
|
|
126
|
+
# request.Context.StoredMeta is of the format: Dict[str, str]
|
|
127
|
+
context = request.Context.StoredMeta
|
|
128
|
+
|
|
129
|
+
currentExecTime = datetime.datetime.now()
|
|
130
|
+
currentExecTime = currentExecTime.strftime("%d-%m-%YT%H-%M-%S")
|
|
131
|
+
resp_message = "Echo message: {} Time:: {}".format(prompt, currentExecTime)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# platform.getRequestFile: Fetch Files specified under the "REQUEST_DIR" in config
|
|
135
|
+
with platform.getRequestFile("myDetails.txt", "r") as reqFile:
|
|
136
|
+
|
|
137
|
+
resp_message += "\n{}".format("\n".join(reqFile.readlines()))
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# Fill context according to your needs
|
|
141
|
+
context[currentExecTime] = resp_message
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# platform.saveRequestFile: Creates any file type Outputs
|
|
145
|
+
# These files would be present under TEMP_EXEC_DIR/execTmp/outputs_<RequestLaunchTimeStamp>__<RequestId>
|
|
146
|
+
# TEMP_EXEC_DIR: Configured under the config.json
|
|
147
|
+
# execTmp: Creates enviroment for the AppEngine under the specified TEMP_EXEC_DIR
|
|
148
|
+
with platform.saveRequestFile("final_resp_{}.txt".format(currentExecTime), "w+") as outFile:
|
|
149
|
+
|
|
150
|
+
outFile.write(resp_message)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
cost = len(prompt)/1000 * 0.03 + len(resp_message)/1000 * 0.06
|
|
154
|
+
response.AdditionalInferenceCosts = cost
|
|
155
|
+
response.Outcomes['OutputMessage'] = [OutcomeValue(value=resp_message, probability=1.0)]
|
|
156
|
+
|
|
157
|
+
# Set the updated context back to the response
|
|
158
|
+
response.Context.StoredMeta = context
|
|
159
|
+
except Exception as e:
|
|
160
|
+
response.ErrorMessages = "Entrypoint Exception Occured: {}".format(str(e))
|
|
161
|
+
|
|
162
|
+
print("Finished Running User Code...")
|
|
163
|
+
return response
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
* Some APIs for using the AppEngine environment
|
|
169
|
+
* **request.Context.StoredMeta**:
|
|
170
|
+
* Saved context across requests
|
|
171
|
+
* Use this variable to save new context in the dict format
|
|
172
|
+
* request.Context.StoredMeta is of the format: Dict[str, str]
|
|
173
|
+
|
|
174
|
+
* **platform.getRequestFile**:
|
|
175
|
+
* Fetch Files specified under the "**REQUEST_DIR**" in config
|
|
176
|
+
|
|
177
|
+
* **platform.saveRequestFile**:
|
|
178
|
+
* Creates any file type Outputs
|
|
179
|
+
* These files would be present under **TEMP_EXEC_DIR/execTmp/*outputs_RequestLaunchTimeStamp__RequestId***
|
|
180
|
+
* **TEMP_EXEC_DIR**: Configured under the config.json
|
|
181
|
+
* execTmp: Creates enviroment for the AppEngine under the specified TEMP_EXEC_DIR
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
* Go back to the `main.py` and add the following command to run your `EntryPoint.py`
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
|
|
189
|
+
# Previous Snippet for initialization
|
|
190
|
+
platformInit = UnityPredictHost()
|
|
191
|
+
|
|
192
|
+
configStat = platformInit.isConfigInitialized()
|
|
193
|
+
|
|
194
|
+
if not configStat:
|
|
195
|
+
print ("Config Initialization Unsuccessful!!")
|
|
196
|
+
sys.exit(0)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
print ("Config Initialization Successful!!")
|
|
200
|
+
|
|
201
|
+
#### New Snippet to run EntryPoint.py via the AppEngine
|
|
202
|
+
|
|
203
|
+
request = Models.AppEngineRequest(RequestId=str(uuid.uuid4()))
|
|
204
|
+
request.EngineInputData = Models.EngineInputs(InputValues={"InputMessage": "Hi, this is the message to be echoed"}, DesiredOutcomes=[])
|
|
205
|
+
|
|
206
|
+
response : Models.UnityPredictEngineResponse = platformInit.run_engine(request=request)
|
|
207
|
+
|
|
208
|
+
# Print Outputs
|
|
209
|
+
if (response.EngineOutputs != None):
|
|
210
|
+
print ("Output: {}".format(response.EngineOutputs.toJSON()))
|
|
211
|
+
|
|
212
|
+
# Print Error Messages (if any)
|
|
213
|
+
print ("Error Messages: {}".format(response.ErrorMessages))
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
## Contributing
|
|
219
|
+
|
|
220
|
+
## License
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
unitypredict/Models.py
|
|
4
|
+
unitypredict/Platform.py
|
|
5
|
+
unitypredict/UnityPredictLocalHost.py
|
|
6
|
+
unitypredict/__init__.py
|
|
7
|
+
unitypredict/scripts.py
|
|
8
|
+
unitypredict.egg-info/PKG-INFO
|
|
9
|
+
unitypredict.egg-info/SOURCES.txt
|
|
10
|
+
unitypredict.egg-info/dependency_links.txt
|
|
11
|
+
unitypredict.egg-info/entry_points.txt
|
|
12
|
+
unitypredict.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
unitypredict
|