ivoryos 0.1.5__py3-none-any.whl
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.
Potentially problematic release.
This version of ivoryos might be problematic. Click here for more details.
- ivoryos/__init__.py +94 -0
- ivoryos/config.py +46 -0
- ivoryos/routes/__init__.py +0 -0
- ivoryos/routes/auth/__init__.py +0 -0
- ivoryos/routes/auth/auth.py +65 -0
- ivoryos/routes/auth/templates/auth/login.html +25 -0
- ivoryos/routes/auth/templates/auth/signup.html +32 -0
- ivoryos/routes/control/__init__.py +0 -0
- ivoryos/routes/control/control.py +233 -0
- ivoryos/routes/control/templates/control/controllers.html +71 -0
- ivoryos/routes/control/templates/control/controllers_home.html +50 -0
- ivoryos/routes/control/templates/control/controllers_new.html +89 -0
- ivoryos/routes/database/__init__.py +0 -0
- ivoryos/routes/database/database.py +122 -0
- ivoryos/routes/database/templates/database/experiment_database.html +72 -0
- ivoryos/routes/design/__init__.py +0 -0
- ivoryos/routes/design/design.py +396 -0
- ivoryos/routes/design/templates/design/experiment_builder.html +413 -0
- ivoryos/routes/design/templates/design/experiment_run.html +325 -0
- ivoryos/routes/main/__init__.py +0 -0
- ivoryos/routes/main/main.py +25 -0
- ivoryos/routes/main/templates/main/help.html +144 -0
- ivoryos/routes/main/templates/main/home.html +68 -0
- ivoryos/static/favicon.ico +0 -0
- ivoryos/static/gui_annotation/Slide1.png +0 -0
- ivoryos/static/gui_annotation/Slide2.PNG +0 -0
- ivoryos/static/js/overlay.js +12 -0
- ivoryos/static/js/socket_handler.js +25 -0
- ivoryos/static/js/sortable_card.js +24 -0
- ivoryos/static/js/sortable_design.js +36 -0
- ivoryos/static/logo.png +0 -0
- ivoryos/static/style.css +202 -0
- ivoryos/templates/base.html +141 -0
- ivoryos/utils/__init__.py +0 -0
- ivoryos/utils/db_models.py +501 -0
- ivoryos/utils/form.py +316 -0
- ivoryos/utils/global_config.py +68 -0
- ivoryos/utils/llm_agent.py +183 -0
- ivoryos/utils/script_runner.py +158 -0
- ivoryos/utils/task_manager.py +80 -0
- ivoryos/utils/utils.py +337 -0
- ivoryos-0.1.5.dist-info/LICENSE +21 -0
- ivoryos-0.1.5.dist-info/METADATA +96 -0
- ivoryos-0.1.5.dist-info/RECORD +46 -0
- ivoryos-0.1.5.dist-info/WHEEL +5 -0
- ivoryos-0.1.5.dist-info/top_level.txt +1 -0
ivoryos/utils/form.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
from wtforms.fields.core import Field
|
|
2
|
+
from wtforms.utils import UnsetValue
|
|
3
|
+
from wtforms.validators import InputRequired
|
|
4
|
+
from wtforms.widgets.core import TextInput
|
|
5
|
+
|
|
6
|
+
from flask_wtf import FlaskForm
|
|
7
|
+
from wtforms import StringField, FloatField, HiddenField, BooleanField, IntegerField
|
|
8
|
+
import inspect
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def find_variable(data, script):
|
|
12
|
+
# TODO: needs to check for valid order of variables, important when editting
|
|
13
|
+
added_variables: list[dict[str, str]] = [action for action in script.currently_editing_script if
|
|
14
|
+
action["instrument"] == "variable"
|
|
15
|
+
# or action["return"] # TODO find returns
|
|
16
|
+
]
|
|
17
|
+
for added_variable in added_variables:
|
|
18
|
+
if added_variable["action"] == data:
|
|
19
|
+
return data, added_variable["args"]
|
|
20
|
+
# if added_variable["return"] == data:
|
|
21
|
+
# return data, None
|
|
22
|
+
return None, None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class VariableOrStringField(Field):
|
|
26
|
+
widget = TextInput()
|
|
27
|
+
|
|
28
|
+
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
29
|
+
super(VariableOrStringField, self).__init__(label, validators, **kwargs)
|
|
30
|
+
self.script = script
|
|
31
|
+
|
|
32
|
+
def process_formdata(self, valuelist):
|
|
33
|
+
if valuelist:
|
|
34
|
+
if not self.script.editing_type == "script" and valuelist[0].startswith("#"):
|
|
35
|
+
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
36
|
+
self.data = valuelist[0]
|
|
37
|
+
|
|
38
|
+
def _value(self):
|
|
39
|
+
if self.script:
|
|
40
|
+
variable, value = find_variable(self.data, self.script)
|
|
41
|
+
if variable:
|
|
42
|
+
return variable
|
|
43
|
+
|
|
44
|
+
return str(self.data) if self.data is not None else ""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class VariableOrFloatField(Field):
|
|
48
|
+
widget = TextInput()
|
|
49
|
+
|
|
50
|
+
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
51
|
+
super(VariableOrFloatField, self).__init__(label, validators, **kwargs)
|
|
52
|
+
self.script = script
|
|
53
|
+
|
|
54
|
+
def _value(self):
|
|
55
|
+
if self.script:
|
|
56
|
+
variable, value = find_variable(self.data, self.script)
|
|
57
|
+
if variable:
|
|
58
|
+
return variable
|
|
59
|
+
|
|
60
|
+
if self.raw_data:
|
|
61
|
+
return self.raw_data[0]
|
|
62
|
+
if self.data is not None:
|
|
63
|
+
return str(self.data)
|
|
64
|
+
return ""
|
|
65
|
+
|
|
66
|
+
def process_formdata(self, valuelist):
|
|
67
|
+
if not valuelist:
|
|
68
|
+
return
|
|
69
|
+
elif valuelist[0].startswith("#"):
|
|
70
|
+
if not self.script.editing_type == "script":
|
|
71
|
+
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
72
|
+
self.data = valuelist[0]
|
|
73
|
+
return
|
|
74
|
+
try:
|
|
75
|
+
if self.script:
|
|
76
|
+
try:
|
|
77
|
+
variable, value = find_variable(valuelist[0], self.script)
|
|
78
|
+
if variable:
|
|
79
|
+
float(value)
|
|
80
|
+
self.data = str(variable)
|
|
81
|
+
return
|
|
82
|
+
except ValueError:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
self.data = float(valuelist[0])
|
|
86
|
+
except ValueError as exc:
|
|
87
|
+
self.data = None
|
|
88
|
+
raise ValueError(self.gettext("Not a valid float value.")) from exc
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
unset_value = UnsetValue()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class VariableOrIntField(Field):
|
|
95
|
+
widget = TextInput()
|
|
96
|
+
|
|
97
|
+
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
98
|
+
super(VariableOrIntField, self).__init__(label, validators, **kwargs)
|
|
99
|
+
self.script = script
|
|
100
|
+
|
|
101
|
+
def _value(self):
|
|
102
|
+
if self.script:
|
|
103
|
+
variable, value = find_variable(self.data, self.script)
|
|
104
|
+
if variable:
|
|
105
|
+
return variable
|
|
106
|
+
|
|
107
|
+
if self.raw_data:
|
|
108
|
+
return self.raw_data[0]
|
|
109
|
+
if self.data is not None:
|
|
110
|
+
return str(self.data)
|
|
111
|
+
return ""
|
|
112
|
+
|
|
113
|
+
# def process_data(self, value):
|
|
114
|
+
#
|
|
115
|
+
# if self.script:
|
|
116
|
+
# variable, var_value = find_variable(value, self.script)
|
|
117
|
+
# if variable:
|
|
118
|
+
# try:
|
|
119
|
+
# int(var_value)
|
|
120
|
+
# self.data = str(variable)
|
|
121
|
+
# return
|
|
122
|
+
# except ValueError:
|
|
123
|
+
# pass
|
|
124
|
+
# if value is None or value is unset_value:
|
|
125
|
+
# self.data = None
|
|
126
|
+
# return
|
|
127
|
+
# try:
|
|
128
|
+
# self.data = int(value)
|
|
129
|
+
# except (ValueError, TypeError) as exc:
|
|
130
|
+
# self.data = None
|
|
131
|
+
# raise ValueError(self.gettext("Not a valid integer value.")) from exc
|
|
132
|
+
|
|
133
|
+
def process_formdata(self, valuelist):
|
|
134
|
+
if not valuelist:
|
|
135
|
+
return
|
|
136
|
+
if self.script:
|
|
137
|
+
variable, var_value = find_variable(valuelist[0], self.script)
|
|
138
|
+
if variable:
|
|
139
|
+
try:
|
|
140
|
+
int(var_value)
|
|
141
|
+
self.data = str(variable)
|
|
142
|
+
return
|
|
143
|
+
except ValueError:
|
|
144
|
+
pass
|
|
145
|
+
if valuelist[0].startswith("#"):
|
|
146
|
+
if not self.script.editing_type == "script":
|
|
147
|
+
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
148
|
+
self.data = valuelist[0]
|
|
149
|
+
return
|
|
150
|
+
try:
|
|
151
|
+
self.data = int(valuelist[0])
|
|
152
|
+
except ValueError as exc:
|
|
153
|
+
self.data = None
|
|
154
|
+
raise ValueError(self.gettext("Not a valid integer value.")) from exc
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class VariableOrBoolField(BooleanField):
|
|
158
|
+
widget = TextInput()
|
|
159
|
+
|
|
160
|
+
def __init__(self, label='', validators=None, script=None, **kwargs):
|
|
161
|
+
super(VariableOrBoolField, self).__init__(label, validators, **kwargs)
|
|
162
|
+
self.script = script
|
|
163
|
+
|
|
164
|
+
def process_data(self, value):
|
|
165
|
+
|
|
166
|
+
if self.script:
|
|
167
|
+
variable, var_value = find_variable(value, self.script)
|
|
168
|
+
if variable:
|
|
169
|
+
try:
|
|
170
|
+
bool(var_value)
|
|
171
|
+
return variable
|
|
172
|
+
except ValueError:
|
|
173
|
+
return
|
|
174
|
+
|
|
175
|
+
self.data = bool(value)
|
|
176
|
+
|
|
177
|
+
def process_formdata(self, valuelist):
|
|
178
|
+
if not valuelist or type(valuelist) is list and valuelist[0] == '':
|
|
179
|
+
self.data = False
|
|
180
|
+
elif valuelist and valuelist[0].startswith("#"):
|
|
181
|
+
if not self.script.editing_type == "script":
|
|
182
|
+
raise ValueError(self.gettext("Variable is not supported in prep/cleanup"))
|
|
183
|
+
self.data = valuelist[0]
|
|
184
|
+
else:
|
|
185
|
+
self.data = True
|
|
186
|
+
|
|
187
|
+
def _value(self):
|
|
188
|
+
|
|
189
|
+
if self.script:
|
|
190
|
+
variable, value = find_variable(self.raw_data, self.script)
|
|
191
|
+
if variable:
|
|
192
|
+
return variable
|
|
193
|
+
|
|
194
|
+
if self.raw_data:
|
|
195
|
+
return str(self.raw_data[0])
|
|
196
|
+
return "y"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def format_name(name):
|
|
200
|
+
"""Converts 'example_name' to 'Example Name'."""
|
|
201
|
+
name = name.split(".")[-1]
|
|
202
|
+
text = ' '.join(word for word in name.split('_'))
|
|
203
|
+
return text.capitalize()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def create_form_for_method(method, method_name, autofill, script=None, design=True):
|
|
207
|
+
class DynamicForm(FlaskForm):
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
annotation_mapping = {
|
|
211
|
+
int: (VariableOrIntField if design else IntegerField, 'Enter integer value'),
|
|
212
|
+
float: (VariableOrFloatField if design else FloatField, 'Enter numeric value'),
|
|
213
|
+
str: (VariableOrStringField if design else StringField, 'Enter text'),
|
|
214
|
+
bool: (VariableOrBoolField if design else BooleanField, 'Empty for false')
|
|
215
|
+
}
|
|
216
|
+
sig = method if type(method) is inspect.Signature else inspect.signature(method)
|
|
217
|
+
|
|
218
|
+
for param in sig.parameters.values():
|
|
219
|
+
if param.name == 'self':
|
|
220
|
+
continue
|
|
221
|
+
formatted_param_name = format_name(param.name)
|
|
222
|
+
field_kwargs = {
|
|
223
|
+
"label": formatted_param_name,
|
|
224
|
+
"default": f'#{param.name}' if autofill else (param.default if param.default is not param.empty else ""),
|
|
225
|
+
**({"script": script} if (autofill or design) else {})
|
|
226
|
+
}
|
|
227
|
+
field_class, placeholder_text = annotation_mapping.get(
|
|
228
|
+
param.annotation,
|
|
229
|
+
(VariableOrStringField if design else StringField, f'Enter {param.annotation} value')
|
|
230
|
+
)
|
|
231
|
+
render_kwargs = {"placeholder": placeholder_text}
|
|
232
|
+
|
|
233
|
+
# Create the field with additional rendering kwargs for placeholder text
|
|
234
|
+
field = field_class(**field_kwargs, render_kw=render_kwargs)
|
|
235
|
+
setattr(DynamicForm, param.name, field)
|
|
236
|
+
|
|
237
|
+
# setattr(DynamicForm, f'add', fname)
|
|
238
|
+
return DynamicForm
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# Create forms for each method in DummySDLDeck
|
|
242
|
+
def create_add_form(attr, attr_name, autofill, script=None, design=True):
|
|
243
|
+
dynamic_form = create_form_for_method(attr, attr_name, autofill, script, design)
|
|
244
|
+
if design:
|
|
245
|
+
return_value = StringField(label='Save value as', render_kw={"placeholder": "Optional"})
|
|
246
|
+
setattr(dynamic_form, 'return', return_value)
|
|
247
|
+
hidden_method_name = HiddenField(name=f'hidden_name', render_kw={"value": f'{attr_name}'})
|
|
248
|
+
setattr(dynamic_form, 'hidden_name', hidden_method_name)
|
|
249
|
+
return dynamic_form
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def create_form_from_module(sdl_module, autofill: bool, script=None, design=True):
|
|
253
|
+
# sdl_deck = DummySDLDeck(DummyPump("COM1"), DummyBalance("COM2"))
|
|
254
|
+
method_forms = {}
|
|
255
|
+
for attr_name in dir(sdl_module):
|
|
256
|
+
attr = getattr(sdl_module, attr_name)
|
|
257
|
+
if inspect.ismethod(attr) and not attr_name.startswith('_'):
|
|
258
|
+
form_class = create_add_form(attr, attr_name, autofill, script, design)
|
|
259
|
+
method_forms[attr_name] = form_class()
|
|
260
|
+
return method_forms
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def create_form_from_pseudo(pseudo: dict, autofill: bool, script=None, design=True):
|
|
264
|
+
'''{'dose_liquid': < Signature(amount_in_ml: float, rate_ml_per_minute: float) >}'''
|
|
265
|
+
method_forms = {}
|
|
266
|
+
for attr_name, signature in pseudo.items():
|
|
267
|
+
form_class = create_add_form(signature, attr_name, autofill, script, design)
|
|
268
|
+
method_forms[attr_name] = form_class()
|
|
269
|
+
return method_forms
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def create_builtin_form(logic_type):
|
|
273
|
+
class BuiltinFunctionForm(FlaskForm):
|
|
274
|
+
pass
|
|
275
|
+
|
|
276
|
+
placeholder_text = f'Enter numbers' if logic_type == 'wait' else f'Enter statement'
|
|
277
|
+
description_text = f'Your variable can be numbers, boolean (True or False) or text ("text")' if logic_type == 'variable' else ''
|
|
278
|
+
field_class = FloatField if logic_type == 'wait' else StringField # Default to StringField as a fallback
|
|
279
|
+
field_kwargs = {
|
|
280
|
+
"label": f'statement',
|
|
281
|
+
"validators": [InputRequired()] if logic_type in ['wait', "variable"] else [],
|
|
282
|
+
"description": description_text,
|
|
283
|
+
}
|
|
284
|
+
render_kwargs = {"placeholder": placeholder_text}
|
|
285
|
+
field = field_class(**field_kwargs, render_kw=render_kwargs)
|
|
286
|
+
setattr(BuiltinFunctionForm, "statement", field)
|
|
287
|
+
if logic_type == 'variable':
|
|
288
|
+
variable_field = StringField(label=f'variable', validators=[InputRequired()],
|
|
289
|
+
description="Your variable name cannot include space",
|
|
290
|
+
render_kw=render_kwargs)
|
|
291
|
+
setattr(BuiltinFunctionForm, "variable", variable_field)
|
|
292
|
+
hidden_field = HiddenField(name=f'builtin_name', render_kw={"value": f'{logic_type}'})
|
|
293
|
+
setattr(BuiltinFunctionForm, "builtin_name", hidden_field)
|
|
294
|
+
return BuiltinFunctionForm()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def create_action_button(s: dict):
|
|
298
|
+
style = ""
|
|
299
|
+
if s['instrument'] in ['if', 'while']:
|
|
300
|
+
text = f"{s['action']} {s['args']}"
|
|
301
|
+
style = "background-color: tomato"
|
|
302
|
+
elif s['instrument'] == 'variable':
|
|
303
|
+
text = f"{s['action']} = {s['args']}"
|
|
304
|
+
else:
|
|
305
|
+
# regular action button
|
|
306
|
+
prefix = f"{s['return']} = " if s['return'] else ""
|
|
307
|
+
action_text = f"{s['instrument'].split('.')[-1] if s['instrument'].startswith('deck') else s['instrument']}.{s['action']}"
|
|
308
|
+
arg_string = ""
|
|
309
|
+
if s['args']:
|
|
310
|
+
if type(s['args']) is dict:
|
|
311
|
+
arg_string = "(" + ", ".join([f"{k} = {v}" for k, v in s['args'].items()]) + ")"
|
|
312
|
+
else:
|
|
313
|
+
arg_string = f"= {s['args']}"
|
|
314
|
+
|
|
315
|
+
text = f"{prefix}{action_text} {arg_string}"
|
|
316
|
+
return dict(label=text, style=style, uuid=s["uuid"], id=s["id"])
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# from ivoryos.utils.script_runner import ScriptRunner
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class GlobalConfig:
|
|
5
|
+
_instance = None
|
|
6
|
+
|
|
7
|
+
def __new__(cls, *args, **kwargs):
|
|
8
|
+
if cls._instance is None:
|
|
9
|
+
cls._instance = super(GlobalConfig, cls).__new__(cls, *args, **kwargs)
|
|
10
|
+
cls._instance._deck = None
|
|
11
|
+
cls._instance._agent = None
|
|
12
|
+
cls._instance._defined_variables = {}
|
|
13
|
+
cls._instance._api_variables = set()
|
|
14
|
+
cls._instance._deck_variables = {}
|
|
15
|
+
cls._instance._runner = None
|
|
16
|
+
return cls._instance
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def deck(self):
|
|
20
|
+
return self._deck
|
|
21
|
+
|
|
22
|
+
@deck.setter
|
|
23
|
+
def deck(self, value):
|
|
24
|
+
if self._deck is None:
|
|
25
|
+
self._deck = value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def deck_variables(self):
|
|
30
|
+
return self._deck_variables
|
|
31
|
+
|
|
32
|
+
@deck_variables.setter
|
|
33
|
+
def deck_variables(self, value):
|
|
34
|
+
self._deck_variables = value
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def agent(self):
|
|
39
|
+
return self._agent
|
|
40
|
+
|
|
41
|
+
@agent.setter
|
|
42
|
+
def agent(self, value):
|
|
43
|
+
if self._agent is None:
|
|
44
|
+
self._agent = value
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def defined_variables(self):
|
|
48
|
+
return self._defined_variables
|
|
49
|
+
|
|
50
|
+
@defined_variables.setter
|
|
51
|
+
def defined_variables(self, value):
|
|
52
|
+
self._defined_variables = value
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def api_variables(self):
|
|
56
|
+
return self._api_variables
|
|
57
|
+
|
|
58
|
+
@api_variables.setter
|
|
59
|
+
def api_variables(self, value):
|
|
60
|
+
self._api_variables = value
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def runner(self):
|
|
64
|
+
return self._runner
|
|
65
|
+
|
|
66
|
+
@runner.setter
|
|
67
|
+
def runner(self, value):
|
|
68
|
+
self._runner = value
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from openai import OpenAI, BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# from dotenv import load_dotenv
|
|
10
|
+
# load_dotenv()
|
|
11
|
+
|
|
12
|
+
# host = "137.82.65.246"
|
|
13
|
+
# model = "llama3"
|
|
14
|
+
|
|
15
|
+
# structured output,
|
|
16
|
+
# class Action(BaseModel):
|
|
17
|
+
# action: str
|
|
18
|
+
# args: dict
|
|
19
|
+
# arg_types: dict
|
|
20
|
+
#
|
|
21
|
+
#
|
|
22
|
+
# class ActionPlan(BaseModel):
|
|
23
|
+
# actions: list[Action]
|
|
24
|
+
# # final_answer: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LlmAgent:
|
|
28
|
+
def __init__(self, model="llama3", output_path=os.curdir, host=None):
|
|
29
|
+
self.host = host
|
|
30
|
+
self.base_url = f"http://{self.host}:11434/v1/" if host is not None else ""
|
|
31
|
+
self.model = model
|
|
32
|
+
self.output_path = os.path.join(output_path, "llm_output") if output_path is not None else None
|
|
33
|
+
self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) if host is None else OpenAI(api_key="ollama",
|
|
34
|
+
base_url=self.base_url)
|
|
35
|
+
if self.output_path is not None:
|
|
36
|
+
os.makedirs(self.output_path, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def extract_annotations_docstrings(module_sigs):
|
|
40
|
+
class_str = ""
|
|
41
|
+
|
|
42
|
+
for name, value in module_sigs.items():
|
|
43
|
+
signature = value.get("signature")
|
|
44
|
+
docstring = value.get("docstring")
|
|
45
|
+
class_str += f'\tdef {name}{signature}:\n'
|
|
46
|
+
class_str += f'\t\t"""\n\t\t{docstring}\n\t\t"""' + '\n' if docstring else ''
|
|
47
|
+
class_str = class_str.replace('self, ', '')
|
|
48
|
+
class_str = class_str.replace('self', '')
|
|
49
|
+
name_list = list(module_sigs.keys())
|
|
50
|
+
# print(class_str)
|
|
51
|
+
# with open(os.path.join(self.output_path, "docstring_manual.txt"), "w") as f:
|
|
52
|
+
# f.write(class_str)
|
|
53
|
+
return class_str, name_list
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def parse_code_from_msg(msg):
|
|
57
|
+
msg = msg.strip()
|
|
58
|
+
# print(msg)
|
|
59
|
+
# code_blocks = re.findall(r'```(?:json\s)?(.*?)```', msg, re.DOTALL)
|
|
60
|
+
code_blocks = re.findall(r'\[\s*\{.*?\}\s*\]', msg, re.DOTALL)
|
|
61
|
+
|
|
62
|
+
json_blocks = []
|
|
63
|
+
for block in code_blocks:
|
|
64
|
+
if not block.startswith('['):
|
|
65
|
+
start_index = block.find('[')
|
|
66
|
+
block = block[start_index:]
|
|
67
|
+
block = re.sub(r'//.*', '', block)
|
|
68
|
+
block = block.replace('True', 'true').replace('False', 'false')
|
|
69
|
+
try:
|
|
70
|
+
# Try to parse the block as JSON
|
|
71
|
+
json_data = json.loads(block.strip())
|
|
72
|
+
if isinstance(json_data, list):
|
|
73
|
+
json_blocks = json_data
|
|
74
|
+
except json.JSONDecodeError:
|
|
75
|
+
continue
|
|
76
|
+
return json_blocks
|
|
77
|
+
|
|
78
|
+
def _generate(self, robot_sigs, prompt):
|
|
79
|
+
# deck_info, name_list = self.extract_annotations_docstrings(type(robot))
|
|
80
|
+
deck_info, name_list = self.extract_annotations_docstrings(robot_sigs)
|
|
81
|
+
full_prompt = '''I have some python functions, for example when calling them I want to write them using JSON,
|
|
82
|
+
it is necessary to include all args
|
|
83
|
+
for example
|
|
84
|
+
def dose_solid(amount_in_mg:float, bring_in:bool=True): def analyze():
|
|
85
|
+
dose_solid(3)
|
|
86
|
+
analyze()
|
|
87
|
+
I would want to write to
|
|
88
|
+
[
|
|
89
|
+
{
|
|
90
|
+
"action": "dose_solid",
|
|
91
|
+
"arg_types": {
|
|
92
|
+
"amount_in_mg": "float",
|
|
93
|
+
"bring_in": "bool"
|
|
94
|
+
},
|
|
95
|
+
"args": {
|
|
96
|
+
"amount_in_mg": 3,
|
|
97
|
+
"bring_in": true
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"action": "analyze",
|
|
102
|
+
"arg_types": {},
|
|
103
|
+
"args": {}
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
''' + f'''
|
|
107
|
+
Now these are my callable functions,
|
|
108
|
+
{deck_info}
|
|
109
|
+
and I want you to find the most appropriate function if I want to do these tasks
|
|
110
|
+
"""{prompt}"""
|
|
111
|
+
,and write a list of dictionary in json accordingly. Please only use these action names {name_list},
|
|
112
|
+
can you also help find the default value you can't find the info from my request.
|
|
113
|
+
'''
|
|
114
|
+
if self.output_path is not None:
|
|
115
|
+
with open(os.path.join(self.output_path, "prompt.txt"), "w") as f:
|
|
116
|
+
f.write(full_prompt)
|
|
117
|
+
messages = [{"role": "user",
|
|
118
|
+
"content": full_prompt}, ]
|
|
119
|
+
# if self.host == "openai":
|
|
120
|
+
output = self.client.chat.completions.create(
|
|
121
|
+
messages=messages,
|
|
122
|
+
model=self.model,
|
|
123
|
+
# response_format={"type": "json_object"},
|
|
124
|
+
)
|
|
125
|
+
msg = output.choices[0].message.content
|
|
126
|
+
# msg = output.choices[0].message.parsed
|
|
127
|
+
|
|
128
|
+
code = self.parse_code_from_msg(msg)
|
|
129
|
+
code = [action for action in code if action.get('action', '') in name_list]
|
|
130
|
+
# print('\033[91m', code, '\033[0m')
|
|
131
|
+
return code
|
|
132
|
+
|
|
133
|
+
def generate_code(self, robot_signature, prompt, attempt_allowance: int = 3):
|
|
134
|
+
attempt = 0
|
|
135
|
+
|
|
136
|
+
while attempt < attempt_allowance:
|
|
137
|
+
_code = self._generate(robot_signature, prompt)
|
|
138
|
+
attempt += 1
|
|
139
|
+
if _code:
|
|
140
|
+
break
|
|
141
|
+
|
|
142
|
+
return self.fill_blanks(_code, robot_signature)
|
|
143
|
+
# return code
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def fill_blanks(actions, robot_signature):
|
|
147
|
+
for action in actions:
|
|
148
|
+
action_name = action['action']
|
|
149
|
+
action_signature = robot_signature.get(action_name).get('signature', {})
|
|
150
|
+
args = action.get("args", {})
|
|
151
|
+
arg_types = action.get("arg_types", {})
|
|
152
|
+
for param in action_signature.parameters.values():
|
|
153
|
+
if param.name == 'self':
|
|
154
|
+
continue
|
|
155
|
+
if param.name not in args:
|
|
156
|
+
args[param.name] = param.default if param.default is not param.empty else ''
|
|
157
|
+
arg_types[param.name] = param.annotation.__name__
|
|
158
|
+
action['args'] = args
|
|
159
|
+
action['arg_types'] = arg_types
|
|
160
|
+
return actions
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if __name__ == "__main__":
|
|
164
|
+
from pprint import pprint
|
|
165
|
+
from example.sdl_example.abstract_sdl import deck
|
|
166
|
+
|
|
167
|
+
from utils import parse_functions
|
|
168
|
+
|
|
169
|
+
deck_sig = parse_functions(deck, doc_string=True)
|
|
170
|
+
# llm_agent = LlmAgent(host="openai", model="gpt-3.5-turbo")
|
|
171
|
+
llm_agent = LlmAgent(host="localhost", model="llama3.1")
|
|
172
|
+
# robot = IrohDeck()
|
|
173
|
+
# extract_annotations_docstrings(DummySDLDeck)
|
|
174
|
+
prompt = '''I want to start with dosing 10 mg of current sample, and add 1 mL of toluene
|
|
175
|
+
and equilibrate for 10 minute at 40 degrees, then sample 20 ul of sample to analyze with hplc, and save result'''
|
|
176
|
+
code = llm_agent.generate_code(deck_sig, prompt)
|
|
177
|
+
pprint(code)
|
|
178
|
+
|
|
179
|
+
"""
|
|
180
|
+
I want to dose 10mg, 6mg, 4mg, 3mg, 2mg, 1mg to 6 vials
|
|
181
|
+
I want to add 10 mg to vial a3, and 10 ml of liquid, then shake them for 3 minutes
|
|
182
|
+
|
|
183
|
+
"""
|