ivoryos 0.1.9__py3-none-any.whl → 0.1.10__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.

Files changed (37) hide show
  1. ivoryos/__init__.py +118 -99
  2. ivoryos/config.py +47 -47
  3. ivoryos/routes/auth/auth.py +100 -65
  4. ivoryos/routes/auth/templates/auth/login.html +25 -25
  5. ivoryos/routes/auth/templates/auth/signup.html +32 -32
  6. ivoryos/routes/control/control.py +400 -272
  7. ivoryos/routes/control/templates/control/controllers.html +75 -75
  8. ivoryos/routes/control/templates/control/controllers_home.html +50 -50
  9. ivoryos/routes/control/templates/control/controllers_new.html +89 -89
  10. ivoryos/routes/database/database.py +188 -114
  11. ivoryos/routes/database/templates/database/experiment_database.html +72 -72
  12. ivoryos/routes/design/design.py +541 -416
  13. ivoryos/routes/design/templates/design/experiment_builder.html +415 -415
  14. ivoryos/routes/design/templates/design/experiment_run.html +325 -325
  15. ivoryos/routes/main/main.py +42 -25
  16. ivoryos/routes/main/templates/main/help.html +141 -141
  17. ivoryos/routes/main/templates/main/home.html +68 -68
  18. ivoryos/static/.DS_Store +0 -0
  19. ivoryos/static/js/overlay.js +12 -12
  20. ivoryos/static/js/socket_handler.js +34 -34
  21. ivoryos/static/js/sortable_card.js +24 -24
  22. ivoryos/static/js/sortable_design.js +36 -36
  23. ivoryos/static/style.css +201 -201
  24. ivoryos/templates/base.html +143 -143
  25. ivoryos/utils/db_models.py +518 -518
  26. ivoryos/utils/form.py +316 -316
  27. ivoryos/utils/global_config.py +67 -67
  28. ivoryos/utils/llm_agent.py +183 -183
  29. ivoryos/utils/script_runner.py +165 -164
  30. ivoryos/utils/utils.py +425 -422
  31. ivoryos/version.py +1 -0
  32. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/LICENSE +21 -21
  33. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/METADATA +170 -169
  34. ivoryos-0.1.10.dist-info/RECORD +47 -0
  35. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/WHEEL +1 -1
  36. ivoryos-0.1.9.dist-info/RECORD +0 -45
  37. {ivoryos-0.1.9.dist-info → ivoryos-0.1.10.dist-info}/top_level.txt +0 -0
@@ -1,68 +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_snapshot = {}
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_snapshot(self):
30
- return self._deck_snapshot
31
-
32
- @deck_snapshot.setter
33
- def deck_snapshot(self, value):
34
- self._deck_snapshot = 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):
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_snapshot = {}
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_snapshot(self):
30
+ return self._deck_snapshot
31
+
32
+ @deck_snapshot.setter
33
+ def deck_snapshot(self, value):
34
+ self._deck_snapshot = 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
68
  self._runner = value
@@ -1,183 +1,183 @@
1
- import inspect
2
- import json
3
- import os
4
- import re
5
-
6
- from openai import OpenAI
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
- """
1
+ import inspect
2
+ import json
3
+ import os
4
+ import re
5
+
6
+ from openai import OpenAI
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
+ """