├── chatgpt_ros
├── resource
│ └── chatgpt_ros
├── chatgpt_ros
│ ├── __init__.py
│ ├── chatgpt_action_client.py
│ └── chatgpt_action_server.py
├── setup.cfg
├── package.xml
├── setup.py
└── test
│ ├── test_copyright.py
│ ├── test_pep257.py
│ └── test_flake8.py
├── chatgpt_ros_interfaces
├── msg
│ └── .gitkeep
├── srv
│ ├── SetPrompt.srv
│ └── AddExample.srv
├── action
│ └── Chat.action
├── package.xml
└── CMakeLists.txt
├── chatgpt_ros_turtlesim
├── resource
│ └── chatgpt_ros_turtlesim
├── chatgpt_ros_turtlesim
│ ├── __init__.py
│ └── chat_turtle.py
├── setup.cfg
├── launch
│ └── demo.py
├── package.xml
├── setup.py
└── test
│ ├── test_copyright.py
│ ├── test_pep257.py
│ └── test_flake8.py
├── LICENSE
├── README.md
└── .gitignore
/chatgpt_ros/resource/chatgpt_ros:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/chatgpt_ros/chatgpt_ros/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/chatgpt_ros_interfaces/msg/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/resource/chatgpt_ros_turtlesim:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/chatgpt_ros_turtlesim/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/chatgpt_ros_interfaces/srv/SetPrompt.srv:
--------------------------------------------------------------------------------
1 | string prompt
2 | ---
--------------------------------------------------------------------------------
/chatgpt_ros_interfaces/srv/AddExample.srv:
--------------------------------------------------------------------------------
1 | string user
2 | string assistant
3 | ---
--------------------------------------------------------------------------------
/chatgpt_ros_interfaces/action/Chat.action:
--------------------------------------------------------------------------------
1 | string messages
2 | ---
3 | string content
4 | ---
5 | string delta
--------------------------------------------------------------------------------
/chatgpt_ros/setup.cfg:
--------------------------------------------------------------------------------
1 | [develop]
2 | script-dir=$base/lib/chatgpt_ros
3 | [install]
4 | install-scripts=$base/lib/chatgpt_ros
5 |
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/setup.cfg:
--------------------------------------------------------------------------------
1 | [develop]
2 | script-dir=$base/lib/chatgpt_ros_turtlesim
3 | [install]
4 | install-scripts=$base/lib/chatgpt_ros_turtlesim
5 |
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/launch/demo.py:
--------------------------------------------------------------------------------
1 | from launch import LaunchDescription
2 | from launch_ros.actions import Node
3 |
4 | def generate_launch_description():
5 | return LaunchDescription([
6 | Node(
7 | package='chatgpt_ros',
8 | namespace='/',
9 | executable='chatgpt_action_server',
10 | name='chatgpt_action_server'
11 | ),
12 | Node(
13 | package='turtlesim',
14 | namespace='/',
15 | executable='turtlesim_node',
16 | name='sim'
17 | ),
18 | Node(
19 | package='chatgpt_ros_turtlesim',
20 | namespace='/',
21 | executable='chat_turtle',
22 | name='chat_turtle',
23 | prefix="gnome-terminal --"
24 | )
25 | ])
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | chatgpt_ros_turtlesim
5 | 0.0.0
6 | TODO: Package description
7 | hy
8 | TODO: License declaration
9 |
10 | rclpy
11 | ros2launch
12 | chatgpt_ros_interfaces
13 |
14 | ament_copyright
15 | ament_flake8
16 | ament_pep257
17 | python3-pytest
18 |
19 |
20 | ament_python
21 |
22 |
23 |
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | package_name = 'chatgpt_ros_turtlesim'
4 |
5 | setup(
6 | name=package_name,
7 | version='0.0.0',
8 | packages=[package_name],
9 | data_files=[
10 | ('share/ament_index/resource_index/packages',
11 | ['resource/' + package_name]),
12 | ('share/' + package_name, ['package.xml']),
13 | ('share/' + package_name, ['launch/demo.py']),
14 | ],
15 | install_requires=['setuptools'],
16 | zip_safe=True,
17 | maintainer='hy',
18 | maintainer_email='810130242@qq.com',
19 | description='TODO: Package description',
20 | license='TODO: License declaration',
21 | tests_require=['pytest'],
22 | entry_points={
23 | 'console_scripts': [
24 | 'chat_turtle = chatgpt_ros_turtlesim.chat_turtle:main',
25 | ],
26 | },
27 | )
28 |
--------------------------------------------------------------------------------
/chatgpt_ros/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | chatgpt_ros
5 | 0.0.0
6 | TODO: Package description
7 | hy
8 | TODO: License declaration
9 |
10 | rclpy
11 | std_msgs
12 | std_srvs
13 | chatgpt_ros_interfaces
14 |
15 | ament_copyright
16 | ament_flake8
17 | ament_pep257
18 | python3-pytest
19 |
20 |
21 | ament_python
22 |
23 |
24 |
--------------------------------------------------------------------------------
/chatgpt_ros/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 |
3 | package_name = 'chatgpt_ros'
4 |
5 | setup(
6 | name=package_name,
7 | version='0.0.0',
8 | packages=[package_name],
9 | data_files=[
10 | ('share/ament_index/resource_index/packages',
11 | ['resource/' + package_name]),
12 | ('share/' + package_name, ['package.xml'])
13 | ],
14 | install_requires=['setuptools'],
15 | zip_safe=True,
16 | maintainer='hy',
17 | maintainer_email='810130242@qq.com',
18 | description='TODO: Package description',
19 | license='TODO: License declaration',
20 | tests_require=['pytest'],
21 | entry_points={
22 | 'console_scripts': [
23 | 'chatgpt_action_server = chatgpt_ros.chatgpt_action_server:main',
24 | 'chatgpt_action_client = chatgpt_ros.chatgpt_action_client:main'
25 | ],
26 | },
27 | )
28 |
--------------------------------------------------------------------------------
/chatgpt_ros_interfaces/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | chatgpt_ros_interfaces
5 | 0.0.0
6 | TODO: Package description
7 | hy
8 | TODO: License declaration
9 |
10 | ament_cmake
11 |
12 | ament_lint_auto
13 | ament_lint_common
14 |
15 | rosidl_default_generators
16 | rosidl_default_runtime
17 | rosidl_interface_packages
18 |
19 |
20 | ament_cmake
21 |
22 |
23 |
--------------------------------------------------------------------------------
/chatgpt_ros/test/test_copyright.py:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Open Source Robotics Foundation, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from ament_copyright.main import main
16 | import pytest
17 |
18 |
19 | @pytest.mark.copyright
20 | @pytest.mark.linter
21 | def test_copyright():
22 | rc = main(argv=['.', 'test'])
23 | assert rc == 0, 'Found errors'
24 |
--------------------------------------------------------------------------------
/chatgpt_ros/test/test_pep257.py:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Open Source Robotics Foundation, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from ament_pep257.main import main
16 | import pytest
17 |
18 |
19 | @pytest.mark.linter
20 | @pytest.mark.pep257
21 | def test_pep257():
22 | rc = main(argv=['.', 'test'])
23 | assert rc == 0, 'Found code style errors / warnings'
24 |
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/test/test_copyright.py:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Open Source Robotics Foundation, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from ament_copyright.main import main
16 | import pytest
17 |
18 |
19 | @pytest.mark.copyright
20 | @pytest.mark.linter
21 | def test_copyright():
22 | rc = main(argv=['.', 'test'])
23 | assert rc == 0, 'Found errors'
24 |
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/test/test_pep257.py:
--------------------------------------------------------------------------------
1 | # Copyright 2015 Open Source Robotics Foundation, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from ament_pep257.main import main
16 | import pytest
17 |
18 |
19 | @pytest.mark.linter
20 | @pytest.mark.pep257
21 | def test_pep257():
22 | rc = main(argv=['.', 'test'])
23 | assert rc == 0, 'Found code style errors / warnings'
24 |
--------------------------------------------------------------------------------
/chatgpt_ros/test/test_flake8.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Open Source Robotics Foundation, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from ament_flake8.main import main_with_errors
16 | import pytest
17 |
18 |
19 | @pytest.mark.flake8
20 | @pytest.mark.linter
21 | def test_flake8():
22 | rc, errors = main_with_errors(argv=[])
23 | assert rc == 0, \
24 | 'Found %d code style errors / warnings:\n' % len(errors) + \
25 | '\n'.join(errors)
26 |
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/test/test_flake8.py:
--------------------------------------------------------------------------------
1 | # Copyright 2017 Open Source Robotics Foundation, Inc.
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 |
15 | from ament_flake8.main import main_with_errors
16 | import pytest
17 |
18 |
19 | @pytest.mark.flake8
20 | @pytest.mark.linter
21 | def test_flake8():
22 | rc, errors = main_with_errors(argv=[])
23 | assert rc == 0, \
24 | 'Found %d code style errors / warnings:\n' % len(errors) + \
25 | '\n'.join(errors)
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 交叉坐标的星辰
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/chatgpt_ros_interfaces/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.5)
2 | project(chatgpt_ros_interfaces)
3 |
4 | # Default to C99
5 | if(NOT CMAKE_C_STANDARD)
6 | set(CMAKE_C_STANDARD 99)
7 | endif()
8 |
9 | # Default to C++14
10 | if(NOT CMAKE_CXX_STANDARD)
11 | set(CMAKE_CXX_STANDARD 14)
12 | endif()
13 |
14 | if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
15 | add_compile_options(-Wall -Wextra -Wpedantic)
16 | endif()
17 |
18 | # find dependencies
19 | find_package(ament_cmake REQUIRED)
20 | # uncomment the following section in order to fill in
21 | # further dependencies manually.
22 | # find_package( REQUIRED)
23 |
24 | find_package(geometry_msgs REQUIRED)
25 | find_package(rosidl_default_generators REQUIRED)
26 |
27 | rosidl_generate_interfaces(${PROJECT_NAME}
28 | "action/Chat.action"
29 | "srv/SetPrompt.srv"
30 | "srv/AddExample.srv"
31 | )
32 |
33 | if(BUILD_TESTING)
34 | find_package(ament_lint_auto REQUIRED)
35 | # the following line skips the linter which checks for copyrights
36 | # uncomment the line when a copyright and license is not present in all source files
37 | #set(ament_cmake_copyright_FOUND TRUE)
38 | # the following line skips cpplint (only works in a git repo)
39 | # uncomment the line when this package is not in a git repo
40 | #set(ament_cmake_cpplint_FOUND TRUE)
41 | ament_lint_auto_find_test_dependencies()
42 | endif()
43 |
44 | ament_package()
45 |
--------------------------------------------------------------------------------
/chatgpt_ros/chatgpt_ros/chatgpt_action_client.py:
--------------------------------------------------------------------------------
1 | import rclpy
2 | from rclpy.action import ActionClient
3 | from rclpy.node import Node
4 |
5 | from chatgpt_ros_interfaces.action import Chat
6 |
7 |
8 | class ChatActionClient(Node):
9 |
10 | def __init__(self):
11 | super().__init__('chatgpt_action_client')
12 | self._action_client = ActionClient(self, Chat, 'chat')
13 |
14 | def send_goal(self, messages):
15 | goal_msg = Chat.Goal()
16 | goal_msg.messages = messages
17 |
18 | self._action_client.wait_for_server()
19 |
20 | self._send_goal_future = self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback)
21 |
22 | self._send_goal_future.add_done_callback(self.goal_response_callback)
23 |
24 | def goal_response_callback(self, future):
25 | goal_handle = future.result()
26 | if not goal_handle.accepted:
27 | return
28 |
29 | self._get_result_future = goal_handle.get_result_async()
30 | self._get_result_future.add_done_callback(self.get_result_callback)
31 |
32 | def get_result_callback(self, future):
33 | print()
34 | self.send_goal(input("Input: "))
35 |
36 | def feedback_callback(self, feedback_msg):
37 | feedback = feedback_msg.feedback
38 | print(feedback.delta, end='', flush=True)
39 |
40 |
41 | def main(args=None):
42 | rclpy.init(args=args)
43 |
44 | action_client = ChatActionClient()
45 |
46 | action_client.send_goal(input("Input: "))
47 |
48 | rclpy.spin(action_client)
49 |
50 |
51 | if __name__ == '__main__':
52 | main()
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chatgpt_ros
2 |
3 | https://user-images.githubusercontent.com/59406481/230091094-198598c8-d4c2-4cd6-ad87-e34df3f56664.mp4
4 |
5 | ## Quick Start
6 |
7 | First you need to get your api key from openai and set it as an environment variable.
8 |
9 | ```
10 | export OPENAI_API_KEY=
11 | ```
12 |
13 | After that you can start the action server and thus establish communication with chatgpt.
14 |
15 | ```
16 | ros2 run chatgpt_ros chatgpt_action_server
17 | ```
18 |
19 | This command starts a node named `/chatgpt_action_server`, which provides the following services and actions.
20 |
21 | Services:
22 | - /reset (std_srvs/srv/Empty): Reset chat history.
23 | - /set_prompt (chatgpt_ros_interfaces/srv/SetPrompt): Set system prompt.
24 | - /add_example (chatgpt_ros_interfaces/srv/AddExample): Add example.
25 |
26 | Actions:
27 | - /chat (chatgpt_ros_interfaces/action/Chat): Chat with ChatGPT.
28 |
29 | This is the description of the `/chat` action.
30 |
31 | ```
32 | string messages
33 | ---
34 | string content
35 | ---
36 | string delta
37 | ```
38 |
39 | The goal (messages) of the chat action is the user's input. The response from chatgpt will keep coming back from feedback (delta). When the chat ends, a full reply (content) is returned.
40 |
41 | You can start an action client to test the `/chat` action.
42 |
43 | ```
44 | ros2 run chatgpt_ros chatgpt_action_client
45 | ```
46 |
47 | ## Turtlesim Demo
48 |
49 | We also provide a demo on how to control the turtlesim in ros using chatgpt. You can start the demo with the following command:
50 |
51 | ```
52 | ros2 launch chatgpt_ros_turtlesim demo.py
53 | ```
54 |
55 | This will pop up a separate command window for entering commands to control the turtle. You can instruct the turtles to draw graphics through natural language.
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | pip-wheel-metadata/
24 | share/python-wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | MANIFEST
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .nox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *.cover
50 | *.py,cover
51 | .hypothesis/
52 | .pytest_cache/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | target/
76 |
77 | # Jupyter Notebook
78 | .ipynb_checkpoints
79 |
80 | # IPython
81 | profile_default/
82 | ipython_config.py
83 |
84 | # pyenv
85 | .python-version
86 |
87 | # pipenv
88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
91 | # install all needed dependencies.
92 | #Pipfile.lock
93 |
94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95 | __pypackages__/
96 |
97 | # Celery stuff
98 | celerybeat-schedule
99 | celerybeat.pid
100 |
101 | # SageMath parsed files
102 | *.sage.py
103 |
104 | # Environments
105 | .env
106 | .venv
107 | env/
108 | venv/
109 | ENV/
110 | env.bak/
111 | venv.bak/
112 |
113 | # Spyder project settings
114 | .spyderproject
115 | .spyproject
116 |
117 | # Rope project settings
118 | .ropeproject
119 |
120 | # mkdocs documentation
121 | /site
122 |
123 | # mypy
124 | .mypy_cache/
125 | .dmypy.json
126 | dmypy.json
127 |
128 | # Pyre type checker
129 | .pyre/
130 |
--------------------------------------------------------------------------------
/chatgpt_ros/chatgpt_ros/chatgpt_action_server.py:
--------------------------------------------------------------------------------
1 | import datetime
2 | import os
3 | import json
4 | import urllib.request
5 | import rclpy
6 | from rclpy.action import ActionServer
7 | from rclpy.node import Node
8 |
9 | from chatgpt_ros_interfaces.action import Chat
10 | from chatgpt_ros_interfaces.srv import SetPrompt, AddExample
11 | from std_srvs.srv import Empty
12 |
13 |
14 | class ChatBot:
15 | """
16 | Chatbot class for conversation.
17 | """
18 |
19 | def __init__(self, api_key, api_base, prompt=None):
20 | self.api_key = api_key
21 | self.api_base = api_base
22 | if prompt is None:
23 | current_date = datetime.datetime.now().strftime('%Y-%m-%d')
24 | self.prompt = f"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible. Knowledge cutoff: 2021-09-01, Current date: {current_date}."
25 | else:
26 | self.prompt = prompt
27 | self.reset()
28 |
29 | def _parse_stream(self, s):
30 | """Parse server-sent events"""
31 | for line in s:
32 | line = line.decode('utf-8')
33 | if line.startswith(':'):
34 | continue
35 | if line.strip() == "data: [DONE]":
36 | return None
37 | if line.startswith("data: "):
38 | yield json.loads(line[len("data: "):])
39 |
40 | def _request(self):
41 | """Send request to openai"""
42 | req = urllib.request.Request(
43 | url=f'{self.api_base}/chat/completions',
44 | data=json.dumps({
45 | "model": "gpt-3.5-turbo",
46 | "temperature": 0,
47 | "stream": True,
48 | "messages": self.messages
49 | }).encode('utf-8'),
50 | headers={
51 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36",
52 | "Content-Type": "application/json",
53 | "Authorization": f"Bearer {self.api_key}"
54 | }
55 | )
56 | return self._parse_stream(urllib.request.urlopen(req))
57 |
58 | def chat(self, messages):
59 | """
60 | Chat with chatgpt model.
61 | :param content: User input
62 | :return: A content generator
63 | """
64 | self.messages.append({"role": "user", "content": messages})
65 |
66 | collected_messages = []
67 | for chunk in self._request():
68 | chunk_message = chunk['choices'][0]['delta']
69 | collected_messages.append(chunk_message)
70 | yield chunk_message.get('content', '')
71 |
72 | full_reply_role = ''.join([m.get('role', '') for m in collected_messages])
73 | full_reply_content = ''.join([m.get('content', '') for m in collected_messages])
74 | self.messages.append({"role": full_reply_role, "content": full_reply_content})
75 |
76 | def get_latest_content(self):
77 | return self.messages[-1]['content']
78 |
79 | def reset(self):
80 | self.messages = [
81 | {
82 | "role": "system",
83 | "content": self.prompt
84 | }
85 | ]
86 |
87 |
88 | class ChatActionServer(Node):
89 | def __init__(self):
90 | # initialize node
91 | super().__init__('chatgpt_action_server')
92 |
93 | # load openai api key
94 | api_key = os.getenv("OPENAI_API_KEY")
95 | api_base = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
96 | assert api_key is not None, "OPENAI_API_KEY is not found in environment variables."
97 |
98 | # create chatbot object
99 | self._chatbot = ChatBot(api_key, api_base)
100 |
101 | # create ros action server
102 | self._action_server = ActionServer(self, Chat, 'chat', self._execute_callback)
103 |
104 | # create service
105 | self._reset_service = self.create_service(Empty, 'reset', self._reset_callback)
106 | self._set_prompt_service = self.create_service(SetPrompt, 'set_prompt', self._set_prompt_callback)
107 | self._add_example_service = self.create_service(AddExample, 'add_example', self._add_example_callback)
108 |
109 | def _execute_callback(self, goal_handle):
110 | messages = goal_handle.request.messages
111 | self.get_logger().info(f'Chat goal received: {messages}')
112 |
113 | # stream chat result
114 | feedback_msg = Chat.Feedback()
115 | for res in self._chatbot.chat(messages):
116 | feedback_msg.delta = res
117 | goal_handle.publish_feedback(feedback_msg)
118 |
119 | # chat finished
120 | goal_handle.succeed()
121 |
122 | # return total chat reply
123 | result = Chat.Result()
124 | result.content = self._chatbot.get_latest_content()
125 | self.get_logger().info('Chat finished.')
126 | return result
127 |
128 | def _reset_callback(self, request, response):
129 | """Reset chat history"""
130 | self._chatbot.reset()
131 | return response
132 |
133 | def _set_prompt_callback(self, request, response):
134 | """Set system prompt"""
135 | self._chatbot.prompt = request.prompt
136 | self._chatbot.reset()
137 | self.get_logger().info(f'Chat prompt changed: {self._chatbot.prompt}')
138 | return response
139 |
140 | def _add_example_callback(self, request, response):
141 | """Add example"""
142 | self._chatbot.messages.append({"role": "user", "content": request.user})
143 | self._chatbot.messages.append({"role": "assistant", "content": request.assistant})
144 | return response
145 |
146 |
147 | def main(args=None):
148 | rclpy.init(args=args)
149 |
150 | chat_action_server = ChatActionServer()
151 |
152 | rclpy.spin(chat_action_server)
153 |
154 |
155 | if __name__ == '__main__':
156 | main()
157 |
--------------------------------------------------------------------------------
/chatgpt_ros_turtlesim/chatgpt_ros_turtlesim/chat_turtle.py:
--------------------------------------------------------------------------------
1 | import rclpy
2 | from rclpy.action import ActionClient
3 | from rclpy.node import Node
4 | from rclpy.executors import SingleThreadedExecutor
5 | import threading
6 | import re
7 | import math
8 |
9 | from chatgpt_ros_interfaces.action import Chat
10 | from chatgpt_ros_interfaces.srv import SetPrompt, AddExample
11 | from turtlesim.srv import SetPen, TeleportRelative, TeleportAbsolute
12 | from turtlesim.msg import Pose
13 | from std_srvs.srv import Empty
14 |
15 |
16 | system_prompt = """You are an assistant helping me with the simulator for robots.
17 | When I ask you to do something, you are supposed to give me Python code that is needed to achieve that task using simulator and then an explanation of what that code does.
18 | You are only allowed to use the functions I have defined for you.
19 | You are not to use any other hypothetical functions that you think might exist.
20 | You can use simple Python functions from libraries such as math and numpy."""
21 |
22 | user_prompt = """Here are some functions you can use to command the robot.
23 |
24 | turtlebot.clear() - Delete the turtle's drawings from the screen. Do not move turtle. State and position of the turtle as well as drawings of other turtles are not affected.
25 | turtlebot.forward(distance) - Move the turtle forward by the specified distance, in the direction the turtle is headed.
26 | turtlebot.backward(distance) - Move the turtle backward by distance, opposite to the direction the turtle is headed. Do not change the turtle's heading.
27 | turtlebot.right(angle) - Turn turtle right by degrees.
28 | turtlebot.left(angle) - Turn turtle left by degrees.
29 | turtlebot.setposition(x, y) - Move turtle to an absolute position. If the pen is down, draw line. Do not change the turtle's orientation.
30 | turtlebot.setheading(to_angle) - Set the orientation of the turtle to to_angle.
31 | turtlebot.home() - Move turtle to the origin - coordinates (0,0) - and set its heading to its start-orientation.
32 | turtlebot.pendown() - Pull the pen down - drawing when moving.
33 | turtlebot.penup() - Pull the pen up - no drawing when moving.
34 | turtlebot.get_position() - Return the turtle's current location (x,y).
35 | turtlebot.get_heading() - Return the turtle's current heading.
36 |
37 | A few useful things:
38 | Instead of moveToPositionAsync() or moveToZAsync(), you should use the function setposition() that I have defined for you.
39 | This is a two-dimensional environment, and setposition() accepts only 2 parameters.
40 | Note that the robot is initially in the center of a square area of size 10*10, and you are not allowed to let the robot touch the boundary of the area."""
41 |
42 | examples = [
43 | {
44 | "user": "move 2 units forward",
45 | "assistant": """```python
46 | turtlebot.forward(10)
47 | ```
48 | This code uses the `forward()` function to move the robot to a new position that is 2 units before the current position."""
49 | }
50 | ]
51 |
52 |
53 | class TurtleBot:
54 | def __init__(self, node, turtle_namespace='/turtle1'):
55 | self._node = node
56 | self._set_pen_cli = node.create_client(SetPen, turtle_namespace + "/set_pen")
57 | self._teleport_relative_cli = node.create_client(TeleportRelative, turtle_namespace + "/teleport_relative")
58 | self._teleport_absolute_cli = node.create_client(TeleportAbsolute, turtle_namespace + "/teleport_absolute")
59 | self._clear_cli = node.create_client(Empty, "/clear")
60 | self._pose_sub = node.create_subscription(Pose, turtle_namespace + "/pose", self._pose_callback, 1)
61 | self._origin = [5.544444561004639, 5.544444561004639, 0.0]
62 | self._pose = [0.0, 0.0, 0.0]
63 |
64 | self.home()
65 |
66 | def _pose_callback(self, msg):
67 | self._pose[0] = msg.x - self._origin[0]
68 | self._pose[1] = msg.y - self._origin[1]
69 | self._pose[2] = msg.theta - - self._origin[2]
70 |
71 | def _degree_to_radian(self, angle):
72 | return angle * math.pi / 180.0
73 |
74 | def clear(self):
75 | """Delete the turtle's drawings from the screen. Do not move turtle. State and position of the turtle as well as drawings of other turtles are not affected.
76 | """
77 | rclpy.spin_until_future_complete(self._node, self._clear_cli.call_async(Empty.Request()))
78 |
79 | def forward(self, distance):
80 | """Move the turtle forward by the specified distance, in the direction the turtle is headed.
81 |
82 | Args:
83 | distance (integer or float): a number
84 | """
85 | req = TeleportRelative.Request()
86 | req.linear = float(distance)
87 | rclpy.spin_until_future_complete(self._node, self._teleport_relative_cli.call_async(req))
88 |
89 |
90 | def backward(self, distance):
91 | """Move the turtle backward by distance, opposite to the direction the turtle is headed. Do not change the turtle's heading.
92 |
93 | Args:
94 | distance (integer or float): a number
95 | """
96 | req = TeleportRelative.Request()
97 | req.linear = -float(distance)
98 | rclpy.spin_until_future_complete(self._node, self._teleport_relative_cli.call_async(req))
99 |
100 | def right(self, angle):
101 | """Turn turtle right by degrees.
102 |
103 | Args:
104 | angle (integer or float): a number
105 | """
106 | req = TeleportRelative.Request()
107 | req.angular = -self._degree_to_radian(angle)
108 | rclpy.spin_until_future_complete(self._node, self._teleport_relative_cli.call_async(req))
109 |
110 | def left(self, angle):
111 | """Turn turtle left by degrees.
112 |
113 | Args:
114 | angle (integer or float): a number
115 | """
116 | req = TeleportRelative.Request()
117 | req.angular = self._degree_to_radian(angle)
118 | rclpy.spin_until_future_complete(self._node, self._teleport_relative_cli.call_async(req))
119 |
120 | def setposition(self, x, y):
121 | """Move turtle to an absolute position. If the pen is down, draw line. Do not change the turtle's orientation.
122 |
123 | Args:
124 | x (integer or float): a number
125 | y (integer or float): a number
126 | """
127 | req = TeleportAbsolute.Request()
128 | req.x = float(x) + self._origin[0]
129 | req.y = float(y) + self._origin[1]
130 | req.theta = self._pose[2] + self._origin[2]
131 | rclpy.spin_until_future_complete(self._node, self._teleport_absolute_cli.call_async(req))
132 |
133 | def setheading(self, to_angle):
134 | """Set the orientation of the turtle to to_angle.
135 |
136 | Args:
137 | to_angle (integer or float): a number
138 | """
139 | req = TeleportAbsolute.Request()
140 | req.x = self._pose[0] + self._origin[0]
141 | req.y = self._pose[1] + self._origin[1]
142 | req.theta = to_angle + self._origin[2]
143 | rclpy.spin_until_future_complete(self._node, self._teleport_absolute_cli.call_async(req))
144 |
145 | def home(self):
146 | """Move turtle to the origin - coordinates (5.0, 5.0) - and set its heading to its start-orientation.
147 | """
148 | req = TeleportAbsolute.Request()
149 | req.x = self._origin[0]
150 | req.y = self._origin[1]
151 | req.theta = self._origin[2]
152 | rclpy.spin_until_future_complete(self._node, self._teleport_absolute_cli.call_async(req))
153 |
154 | def pendown(self):
155 | """Pull the pen down - drawing when moving.
156 | """
157 | req = SetPen.Request()
158 | req.r = 255
159 | req.g = 255
160 | req.b = 255
161 | req.width = 2
162 | req.off = 0
163 | rclpy.spin_until_future_complete(self._node, self._set_pen_cli.call_async(req))
164 |
165 | def penup(self):
166 | """Pull the pen up - no drawing when moving.
167 | """
168 | req = SetPen.Request()
169 | req.r = 255
170 | req.g = 255
171 | req.b = 255
172 | req.width = 2
173 | req.off = 1
174 | rclpy.spin_until_future_complete(self._node, self._set_pen_cli.call_async(req))
175 |
176 | def get_position(self):
177 | """Return the turtle's current location (x,y).
178 |
179 | Returns:
180 | list[float]: a list of numbers
181 | """
182 | return self._pose[:2]
183 |
184 | def get_heading(self):
185 | """Return the turtle's current heading.
186 |
187 | Returns:
188 | float: a number
189 | """
190 | return self._pose[2]
191 |
192 |
193 | class ChatTurlteClient(Node):
194 | def __init__(self):
195 | super().__init__('chat_action_client')
196 | self._action_client = ActionClient(self, Chat, 'chat')
197 | self._set_prompt_cli = self.create_client(SetPrompt, "set_prompt")
198 | self._add_example_cli = self.create_client(AddExample, "add_example")
199 |
200 | def initialize(self):
201 | req = SetPrompt.Request()
202 | req.prompt = system_prompt
203 | self._set_prompt_cli.wait_for_service()
204 | set_prompt_future = self._set_prompt_cli.call_async(req)
205 | rclpy.spin_until_future_complete(self, set_prompt_future)
206 |
207 | for ex in examples:
208 | req = AddExample.Request()
209 | req.user = ex['user']
210 | req.assistant = ex['assistant']
211 | self._add_example_cli.wait_for_service()
212 | add_example_future = self._add_example_cli.call_async(req)
213 | rclpy.spin_until_future_complete(self, add_example_future)
214 |
215 | self.send_goal(user_prompt, slient=True)
216 |
217 | def send_goal(self, messages, slient=False):
218 | goal_msg = Chat.Goal()
219 | goal_msg.messages = messages
220 |
221 | self._action_client.wait_for_server()
222 | if slient:
223 | send_goal_future = self._action_client.send_goal_async(goal_msg)
224 | else:
225 | send_goal_future = self._action_client.send_goal_async(goal_msg, feedback_callback=self.feedback_callback)
226 | rclpy.spin_until_future_complete(self, send_goal_future)
227 |
228 | get_result_future = send_goal_future.result().get_result_async()
229 | rclpy.spin_until_future_complete(self, get_result_future)
230 | if not slient:
231 | print()
232 |
233 | return get_result_future.result().result.content
234 |
235 | def feedback_callback(self, feedback_msg):
236 | feedback = feedback_msg.feedback
237 | print(feedback.delta, end='', flush=True)
238 |
239 |
240 | def extract_python_code(content):
241 | code_block_regex = re.compile(r"```(.*?)```", re.DOTALL)
242 | code_blocks = code_block_regex.findall(content)
243 | if code_blocks:
244 | full_code = "\n".join(code_blocks)
245 |
246 | if full_code.startswith("python"):
247 | full_code = full_code[7:]
248 |
249 | return full_code
250 | else:
251 | return None
252 |
253 |
254 | def interact_function(node):
255 | turtlebot = TurtleBot(node)
256 | while rclpy.ok():
257 | user_input = input('user: ')
258 | reply = node.send_goal(user_input)
259 | code = extract_python_code(reply)
260 | if code is not None:
261 | try:
262 | exec(code)
263 | except Exception as e:
264 | node.send_goal(str(e))
265 |
266 |
267 | def main(args=None):
268 | rclpy.init(args=args)
269 |
270 | node = ChatTurlteClient()
271 | node.initialize()
272 |
273 | interact_thread = threading.Thread(target=interact_function, args=(node,))
274 | interact_thread.run()
275 |
276 | executer = SingleThreadedExecutor()
277 | executer.add_node(node)
278 | executer.spin()
279 |
280 |
281 | if __name__ == '__main__':
282 | main()
283 |
--------------------------------------------------------------------------------