Spaces:
Runtime error
Runtime error
jackyliang42
commited on
Commit
•
47097db
1
Parent(s):
9a40e4f
working logging, readme
Browse files- LICENSE.md +7 -0
- README.md +45 -1
- app.py +28 -16
- lmp.py +12 -5
- md_logger.py +16 -0
- prompts/parse_obj_name.py +5 -10
LICENSE.md
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Copyright 2021 Google LLC. SPDX-License-Identifier: Apache-2.0
|
2 |
+
|
3 |
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
4 |
+
|
5 |
+
https://www.apache.org/licenses/LICENSE-2.0
|
6 |
+
|
7 |
+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
README.md
CHANGED
@@ -10,4 +10,48 @@ pinned: false
|
|
10 |
license: apache-2.0
|
11 |
---
|
12 |
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
license: apache-2.0
|
11 |
---
|
12 |
|
13 |
+
# Code as Policies Tabletop Manipulation Interactive Demo
|
14 |
+
|
15 |
+
This notebook is a part of the open-source code release associated with the paper:
|
16 |
+
|
17 |
+
[Code as Policies: Language Model Programs for Embodied Control](https://code-as-policies.github.io/)
|
18 |
+
|
19 |
+
This notebook gives an interactive demo for the simulated tabletop manipulation domain, seen in the paper section IV.D
|
20 |
+
|
21 |
+
## Preparations:
|
22 |
+
|
23 |
+
1) Obtain an [OpenAI API Key](https://openai.com/blog/openai-api/)
|
24 |
+
|
25 |
+
2) Gain Codex access by [joining the waitlist](https://openai.com/blog/openai-codex/)
|
26 |
+
|
27 |
+
Once you have Codex access you can use `code-davinci-002`. Using the GPT-3 model (`text-dainvci-002`) is also ok, but performance won't be as good (there will be more code logic errors).
|
28 |
+
|
29 |
+
## Instructions:
|
30 |
+
|
31 |
+
1. Fill in the API Key, model name, and how many blocks and bowls to be spawned in the environment.
|
32 |
+
2. Click Setup/Reset Env
|
33 |
+
3. Based on the new randomly sampled object names, input an instruction and click Run Instruction. If successful, this will render a video and update the simulation environment visualization.
|
34 |
+
|
35 |
+
You can run instructions in sequence and refer back to previous commands (e.g. do the same with other blocks, move the same block to the other bowl, etc). Click Setup/Reset Env to reset, and this will clear the current instruction history.
|
36 |
+
|
37 |
+
Supported commands:
|
38 |
+
* Spatial reasoning (e.g. to the left of the red block, the closest corner, the farthest bowl, the second block from the right)
|
39 |
+
* Sequential actions (e.g. put blocks in matching bowls, stack blocks on the bottom right corner)
|
40 |
+
* Contextual commands (e.g. do the same with the blue block, undo that)
|
41 |
+
* Language-based reasoning (e.g. put the forest-colored block on the ocean-colored bowl).
|
42 |
+
* Simple Q&A (e.g. how many blocks are to the left of the blue bowl?)
|
43 |
+
|
44 |
+
Example commands (note object names may need to be changed depending the sampled object names):
|
45 |
+
* put the sun-colored block on the bowl closest to it
|
46 |
+
* stack the blocks on the bottom most bowl
|
47 |
+
* arrange the blocks as a square in the middle
|
48 |
+
* move the square 5cm to the right
|
49 |
+
* how many blocks are to the right of the orange bowl?
|
50 |
+
* pick up the block closest to the top left corner and place it on the bottom right corner
|
51 |
+
|
52 |
+
Known limitations:
|
53 |
+
* In simulation we're using ground truth object poses instead of using vision models. This means that commands the require knowledge of visual apperances (e.g. darkest bowl, largest object) are not supported.
|
54 |
+
* Currently, the low-level pick place primitive does not do collision checking, so if there are many objects on the table, placing actions may incur collisions.
|
55 |
+
* Prompt saturation - if too many commands (10+) are executed in a row, then the LLM may start to ignore examples in the early parts of the prompt.
|
56 |
+
* Ambiguous instructions - if a given instruction doesn't lead to the desired actions, try rephrasing it to remove ambiguities (e.g. place the block on the closest bowl -> place the block on its closest bowl)
|
57 |
+
|
app.py
CHANGED
@@ -9,10 +9,10 @@ from omegaconf import OmegaConf
|
|
9 |
from moviepy.editor import ImageSequenceClip
|
10 |
import gradio as gr
|
11 |
|
12 |
-
|
13 |
from lmp import LMP, LMPFGen
|
14 |
from sim import PickPlaceEnv, LMP_wrapper
|
15 |
from consts import ALL_BLOCKS, ALL_BOWLS
|
|
|
16 |
|
17 |
|
18 |
class DemoRunner:
|
@@ -21,6 +21,7 @@ class DemoRunner:
|
|
21 |
self._cfg = OmegaConf.to_container(OmegaConf.load('cfg.yaml'), resolve=True)
|
22 |
self._env = None
|
23 |
self._model_name = ''
|
|
|
24 |
|
25 |
def make_LMP(self, env):
|
26 |
# LMP env wrapper
|
@@ -49,20 +50,20 @@ class DemoRunner:
|
|
49 |
'get_corner_name', 'get_side_name',
|
50 |
]
|
51 |
}
|
52 |
-
variable_vars['say'] = lambda msg:
|
53 |
|
54 |
# creating the function-generating LMP
|
55 |
-
lmp_fgen = LMPFGen(cfg['lmps']['fgen'], fixed_vars, variable_vars)
|
56 |
|
57 |
# creating other low-level LMPs
|
58 |
variable_vars.update({
|
59 |
-
k: LMP(k, cfg['lmps'][k], lmp_fgen, fixed_vars, variable_vars)
|
60 |
for k in ['parse_obj_name', 'parse_position', 'parse_question', 'transform_shape_pts']
|
61 |
})
|
62 |
|
63 |
# creating the LMP that deals w/ high-level language commands
|
64 |
lmp_tabletop_ui = LMP(
|
65 |
-
'tabletop_ui', cfg['lmps']['tabletop_ui'], lmp_fgen, fixed_vars, variable_vars
|
66 |
)
|
67 |
|
68 |
return lmp_tabletop_ui
|
@@ -89,45 +90,56 @@ class DemoRunner:
|
|
89 |
return 'Please run setup first'
|
90 |
|
91 |
self._env.cache_video = []
|
|
|
92 |
|
93 |
-
|
|
|
|
|
|
|
|
|
94 |
|
95 |
-
video_file_name =
|
96 |
if self._env.cache_video:
|
97 |
rendered_clip = ImageSequenceClip(self._env.cache_video, fps=25)
|
98 |
-
video_file_name = NamedTemporaryFile(suffix='.mp4'
|
99 |
rendered_clip.write_videofile(video_file_name, fps=25)
|
100 |
|
101 |
-
return
|
102 |
|
103 |
|
104 |
if __name__ == '__main__':
|
105 |
demo_runner = DemoRunner()
|
106 |
demo = gr.Blocks()
|
107 |
|
|
|
|
|
|
|
|
|
|
|
108 |
with demo:
|
|
|
109 |
with gr.Row():
|
110 |
with gr.Column():
|
111 |
with gr.Row():
|
112 |
-
inp_api_key = gr.Textbox(label='OpenAI API Key', lines=1
|
113 |
inp_model_name = gr.Dropdown(label='Model Name', choices=['code-davinci-002', 'text-davinci-002'], value='code-davinci-002')
|
114 |
with gr.Row():
|
115 |
inp_n_blocks = gr.Slider(label='Num Blocks', minimum=0, maximum=3, value=3, step=1)
|
116 |
inp_n_bowls = gr.Slider(label='Num Bowls', minimum=0, maximum=3, value=3, step=1)
|
117 |
|
118 |
-
btn_setup = gr.Button("
|
119 |
info_setup = gr.Markdown(label='Setup Info')
|
120 |
with gr.Column():
|
121 |
-
img_setup = gr.Image(label='
|
122 |
|
123 |
with gr.Row():
|
124 |
with gr.Column():
|
125 |
|
126 |
inp_instruction = gr.Textbox(label='Instruction', lines=1)
|
127 |
-
btn_run = gr.Button("
|
128 |
-
info_run = gr.
|
129 |
with gr.Column():
|
130 |
-
video_run = gr.Video(label='
|
131 |
|
132 |
btn_setup.click(
|
133 |
demo_runner.setup,
|
@@ -137,7 +149,7 @@ if __name__ == '__main__':
|
|
137 |
btn_run.click(
|
138 |
demo_runner.run,
|
139 |
inputs=[inp_instruction],
|
140 |
-
outputs=[info_run, video_run]
|
141 |
)
|
142 |
|
143 |
demo.launch()
|
|
|
9 |
from moviepy.editor import ImageSequenceClip
|
10 |
import gradio as gr
|
11 |
|
|
|
12 |
from lmp import LMP, LMPFGen
|
13 |
from sim import PickPlaceEnv, LMP_wrapper
|
14 |
from consts import ALL_BLOCKS, ALL_BOWLS
|
15 |
+
from md_logger import MarkdownLogger
|
16 |
|
17 |
|
18 |
class DemoRunner:
|
|
|
21 |
self._cfg = OmegaConf.to_container(OmegaConf.load('cfg.yaml'), resolve=True)
|
22 |
self._env = None
|
23 |
self._model_name = ''
|
24 |
+
self._md_logger = MarkdownLogger()
|
25 |
|
26 |
def make_LMP(self, env):
|
27 |
# LMP env wrapper
|
|
|
50 |
'get_corner_name', 'get_side_name',
|
51 |
]
|
52 |
}
|
53 |
+
variable_vars['say'] = lambda msg: self._md_logger.log_text(f'Robot says: "{msg}"')
|
54 |
|
55 |
# creating the function-generating LMP
|
56 |
+
lmp_fgen = LMPFGen(cfg['lmps']['fgen'], fixed_vars, variable_vars, self._md_logger)
|
57 |
|
58 |
# creating other low-level LMPs
|
59 |
variable_vars.update({
|
60 |
+
k: LMP(k, cfg['lmps'][k], lmp_fgen, fixed_vars, variable_vars, self._md_logger)
|
61 |
for k in ['parse_obj_name', 'parse_position', 'parse_question', 'transform_shape_pts']
|
62 |
})
|
63 |
|
64 |
# creating the LMP that deals w/ high-level language commands
|
65 |
lmp_tabletop_ui = LMP(
|
66 |
+
'tabletop_ui', cfg['lmps']['tabletop_ui'], lmp_fgen, fixed_vars, variable_vars, self._md_logger
|
67 |
)
|
68 |
|
69 |
return lmp_tabletop_ui
|
|
|
90 |
return 'Please run setup first'
|
91 |
|
92 |
self._env.cache_video = []
|
93 |
+
self._md_logger.clear()
|
94 |
|
95 |
+
try:
|
96 |
+
self._lmp_tabletop_ui(instruction, f'objects = {self._env.object_list}')
|
97 |
+
run_info = self._md_logger.get_log()
|
98 |
+
except Exception as e:
|
99 |
+
run_info = f'Error: {e}'
|
100 |
|
101 |
+
video_file_name = None
|
102 |
if self._env.cache_video:
|
103 |
rendered_clip = ImageSequenceClip(self._env.cache_video, fps=25)
|
104 |
+
video_file_name = NamedTemporaryFile(suffix='.mp4').name
|
105 |
rendered_clip.write_videofile(video_file_name, fps=25)
|
106 |
|
107 |
+
return run_info, self._env.get_camera_image(), video_file_name
|
108 |
|
109 |
|
110 |
if __name__ == '__main__':
|
111 |
demo_runner = DemoRunner()
|
112 |
demo = gr.Blocks()
|
113 |
|
114 |
+
with open('README.md', 'r') as f:
|
115 |
+
for _ in range(12):
|
116 |
+
next(f)
|
117 |
+
readme_text = f.read()
|
118 |
+
|
119 |
with demo:
|
120 |
+
gr.Markdown(readme_text)
|
121 |
with gr.Row():
|
122 |
with gr.Column():
|
123 |
with gr.Row():
|
124 |
+
inp_api_key = gr.Textbox(label='OpenAI API Key', lines=1)
|
125 |
inp_model_name = gr.Dropdown(label='Model Name', choices=['code-davinci-002', 'text-davinci-002'], value='code-davinci-002')
|
126 |
with gr.Row():
|
127 |
inp_n_blocks = gr.Slider(label='Num Blocks', minimum=0, maximum=3, value=3, step=1)
|
128 |
inp_n_bowls = gr.Slider(label='Num Bowls', minimum=0, maximum=3, value=3, step=1)
|
129 |
|
130 |
+
btn_setup = gr.Button("Setup/Reset Env")
|
131 |
info_setup = gr.Markdown(label='Setup Info')
|
132 |
with gr.Column():
|
133 |
+
img_setup = gr.Image(label='Current Simulation')
|
134 |
|
135 |
with gr.Row():
|
136 |
with gr.Column():
|
137 |
|
138 |
inp_instruction = gr.Textbox(label='Instruction', lines=1)
|
139 |
+
btn_run = gr.Button("Run Instruction")
|
140 |
+
info_run = gr.Markdown(label='Generated Code')
|
141 |
with gr.Column():
|
142 |
+
video_run = gr.Video(label='Video of Last Instruction')
|
143 |
|
144 |
btn_setup.click(
|
145 |
demo_runner.setup,
|
|
|
149 |
btn_run.click(
|
150 |
demo_runner.run,
|
151 |
inputs=[inp_instruction],
|
152 |
+
outputs=[info_run, img_setup, video_run]
|
153 |
)
|
154 |
|
155 |
demo.launch()
|
lmp.py
CHANGED
@@ -10,9 +10,10 @@ from pygments.formatters import TerminalFormatter
|
|
10 |
|
11 |
class LMP:
|
12 |
|
13 |
-
def __init__(self, name, cfg, lmp_fgen, fixed_vars, variable_vars):
|
14 |
self._name = name
|
15 |
self._cfg = cfg
|
|
|
16 |
|
17 |
with open(self._cfg['prompt_path'], 'r') as f:
|
18 |
self._base_prompt = f.read()
|
@@ -72,7 +73,9 @@ class LMP:
|
|
72 |
to_log = f'{use_query}\n{to_exec}'
|
73 |
|
74 |
to_log_pretty = highlight(to_log, PythonLexer(), TerminalFormatter())
|
75 |
-
print(f'LMP {self._name}
|
|
|
|
|
76 |
|
77 |
new_fs = self._lmp_fgen.create_new_fs_from_code(code_str)
|
78 |
self._variable_vars.update(new_fs)
|
@@ -94,12 +97,13 @@ class LMP:
|
|
94 |
|
95 |
class LMPFGen:
|
96 |
|
97 |
-
def __init__(self, cfg, fixed_vars, variable_vars):
|
98 |
self._cfg = cfg
|
99 |
|
100 |
self._stop_tokens = list(self._cfg['stop'])
|
101 |
self._fixed_vars = fixed_vars
|
102 |
self._variable_vars = variable_vars
|
|
|
103 |
|
104 |
with open(self._cfg['prompt_path'], 'r') as f:
|
105 |
self._base_prompt = f.read()
|
@@ -142,8 +146,11 @@ class LMPFGen:
|
|
142 |
|
143 |
f = lvars[f_name]
|
144 |
|
145 |
-
to_print =
|
146 |
-
|
|
|
|
|
|
|
147 |
|
148 |
if return_src:
|
149 |
return f, f_src
|
|
|
10 |
|
11 |
class LMP:
|
12 |
|
13 |
+
def __init__(self, name, cfg, lmp_fgen, fixed_vars, variable_vars, md_logger):
|
14 |
self._name = name
|
15 |
self._cfg = cfg
|
16 |
+
self._md_logger = md_logger
|
17 |
|
18 |
with open(self._cfg['prompt_path'], 'r') as f:
|
19 |
self._base_prompt = f.read()
|
|
|
73 |
to_log = f'{use_query}\n{to_exec}'
|
74 |
|
75 |
to_log_pretty = highlight(to_log, PythonLexer(), TerminalFormatter())
|
76 |
+
print(f'LMP {self._name} generated code:\n{to_log_pretty}')
|
77 |
+
self._md_logger.log_text(f'LMP {self._name} Generated Code:')
|
78 |
+
self._md_logger.log_code(to_log)
|
79 |
|
80 |
new_fs = self._lmp_fgen.create_new_fs_from_code(code_str)
|
81 |
self._variable_vars.update(new_fs)
|
|
|
97 |
|
98 |
class LMPFGen:
|
99 |
|
100 |
+
def __init__(self, cfg, fixed_vars, variable_vars, md_logger):
|
101 |
self._cfg = cfg
|
102 |
|
103 |
self._stop_tokens = list(self._cfg['stop'])
|
104 |
self._fixed_vars = fixed_vars
|
105 |
self._variable_vars = variable_vars
|
106 |
+
self._md_logger = md_logger
|
107 |
|
108 |
with open(self._cfg['prompt_path'], 'r') as f:
|
109 |
self._base_prompt = f.read()
|
|
|
146 |
|
147 |
f = lvars[f_name]
|
148 |
|
149 |
+
to_print = f'{use_query}\n{f_src}'
|
150 |
+
to_print_pretty = highlight(to_print, PythonLexer(), TerminalFormatter())
|
151 |
+
print(f'LMPFGen generated code:\n{to_print_pretty}')
|
152 |
+
self._md_logger.log_text('Generated Function:')
|
153 |
+
self._md_logger.log_code(to_print)
|
154 |
|
155 |
if return_src:
|
156 |
return f, f_src
|
md_logger.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class MarkdownLogger:
|
2 |
+
|
3 |
+
def __init__(self):
|
4 |
+
self._log = ''
|
5 |
+
|
6 |
+
def log_text(self, text):
|
7 |
+
self._log += '\n' + text + '\n'
|
8 |
+
|
9 |
+
def log_code(self, code):
|
10 |
+
self._log += f'\n```python\n{code}\n```\n'
|
11 |
+
|
12 |
+
def clear(self):
|
13 |
+
self._log = ''
|
14 |
+
|
15 |
+
def get_log(self):
|
16 |
+
return self._log
|
prompts/parse_obj_name.py
CHANGED
@@ -5,8 +5,7 @@ from utils import get_obj_positions_np
|
|
5 |
objects = ['blue block', 'cyan block', 'purple bowl', 'gray bowl', 'brown bowl', 'pink block', 'purple block']
|
6 |
# the block closest to the purple bowl.
|
7 |
block_names = ['blue block', 'cyan block', 'purple block']
|
8 |
-
|
9 |
-
closest_block_idx = get_closest_idx(points=block_positions, point=get_obj_pos('purple bowl'))
|
10 |
closest_block_name = block_names[closest_block_idx]
|
11 |
ret_val = closest_block_name
|
12 |
objects = ['brown bowl', 'banana', 'brown block', 'apple', 'blue bowl', 'blue block']
|
@@ -37,28 +36,24 @@ objects = ['blue block', 'cyan block', 'purple bowl', 'brown bowl', 'purple bloc
|
|
37 |
# the block closest to the bottom right corner.
|
38 |
corner_pos = parse_position('bottom right corner')
|
39 |
block_names = ['blue block', 'cyan block', 'purple block']
|
40 |
-
|
41 |
-
closest_block_idx = get_closest_idx(points=block_positions, point=corner_pos)
|
42 |
closest_block_name = block_names[closest_block_idx]
|
43 |
ret_val = closest_block_name
|
44 |
objects = ['brown bowl', 'green block', 'brown block', 'green bowl', 'blue bowl', 'blue block']
|
45 |
# the left most block.
|
46 |
block_names = ['green block', 'brown block', 'blue block']
|
47 |
-
|
48 |
-
left_block_idx = np.argsort(block_positions[:, 0])[0]
|
49 |
left_block_name = block_names[left_block_idx]
|
50 |
ret_val = left_block_name
|
51 |
objects = ['brown bowl', 'green block', 'brown block', 'green bowl', 'blue bowl', 'blue block']
|
52 |
# the bowl on near the top.
|
53 |
bowl_names = ['brown bowl', 'green bowl', 'blue bowl']
|
54 |
-
|
55 |
-
top_bowl_idx = np.argsort(block_positions[:, 1])[-1]
|
56 |
top_bowl_name = bowl_names[top_bowl_idx]
|
57 |
ret_val = top_bowl_name
|
58 |
objects = ['yellow bowl', 'purple block', 'yellow block', 'purple bowl', 'pink bowl', 'pink block']
|
59 |
# the third bowl from the right.
|
60 |
bowl_names = ['yellow bowl', 'purple bowl', 'pink bowl']
|
61 |
-
|
62 |
-
bowl_idx = np.argsort(block_positions[:, 0])[-3]
|
63 |
bowl_name = bowl_names[bowl_idx]
|
64 |
ret_val = bowl_name
|
|
|
5 |
objects = ['blue block', 'cyan block', 'purple bowl', 'gray bowl', 'brown bowl', 'pink block', 'purple block']
|
6 |
# the block closest to the purple bowl.
|
7 |
block_names = ['blue block', 'cyan block', 'purple block']
|
8 |
+
closest_block_idx = get_closest_idx(points=get_obj_positions_np(block_names), point=get_obj_pos('purple bowl'))
|
|
|
9 |
closest_block_name = block_names[closest_block_idx]
|
10 |
ret_val = closest_block_name
|
11 |
objects = ['brown bowl', 'banana', 'brown block', 'apple', 'blue bowl', 'blue block']
|
|
|
36 |
# the block closest to the bottom right corner.
|
37 |
corner_pos = parse_position('bottom right corner')
|
38 |
block_names = ['blue block', 'cyan block', 'purple block']
|
39 |
+
closest_block_idx = get_closest_idx(points=get_obj_positions_np(block_names), point=corner_pos)
|
|
|
40 |
closest_block_name = block_names[closest_block_idx]
|
41 |
ret_val = closest_block_name
|
42 |
objects = ['brown bowl', 'green block', 'brown block', 'green bowl', 'blue bowl', 'blue block']
|
43 |
# the left most block.
|
44 |
block_names = ['green block', 'brown block', 'blue block']
|
45 |
+
left_block_idx = np.argsort(get_obj_positions_np(block_names)[:, 0])[0]
|
|
|
46 |
left_block_name = block_names[left_block_idx]
|
47 |
ret_val = left_block_name
|
48 |
objects = ['brown bowl', 'green block', 'brown block', 'green bowl', 'blue bowl', 'blue block']
|
49 |
# the bowl on near the top.
|
50 |
bowl_names = ['brown bowl', 'green bowl', 'blue bowl']
|
51 |
+
top_bowl_idx = np.argsort(get_obj_positions_np(bowl_names)[:, 1])[-1]
|
|
|
52 |
top_bowl_name = bowl_names[top_bowl_idx]
|
53 |
ret_val = top_bowl_name
|
54 |
objects = ['yellow bowl', 'purple block', 'yellow block', 'purple bowl', 'pink bowl', 'pink block']
|
55 |
# the third bowl from the right.
|
56 |
bowl_names = ['yellow bowl', 'purple bowl', 'pink bowl']
|
57 |
+
bowl_idx = np.argsort(get_obj_positions_np(bowl_names)[:, 0])[-3]
|
|
|
58 |
bowl_name = bowl_names[bowl_idx]
|
59 |
ret_val = bowl_name
|