Spaces:
Runtime error
Runtime error
Upload 17 files
Browse files- README.md +112 -5
- app.py +197 -0
- bot_backend.py +232 -0
- config.json +16 -0
- config.json.example +16 -0
- example_img/1.jpg +0 -0
- example_img/2.jpg +0 -0
- example_img/3.jpg +0 -0
- example_img/4.jpg +0 -0
- example_img/5.jpg +0 -0
- example_img/6.jpg +0 -0
- functional.py +116 -0
- gitattributes.txt +35 -0
- github-log.png +0 -0
- jupyter_backend.py +100 -0
- requirements.txt +12 -0
- response_parser.py +200 -0
README.md
CHANGED
|
@@ -1,13 +1,120 @@
|
|
| 1 |
---
|
| 2 |
title: Code Interpreter
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 3.
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: mit
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
title: Code Interpreter
|
| 3 |
+
emoji: 🔥
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: pink
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 3.41.2
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: mit
|
| 11 |
+
duplicated_from: dongsiqie/Code-Interpreter
|
| 12 |
---
|
| 13 |
|
| 14 |
+
# Local-Code-Interpreter
|
| 15 |
+
A local implementation of OpenAI's ChatGPT Code Interpreter.
|
| 16 |
+
|
| 17 |
+
## Introduction
|
| 18 |
+
|
| 19 |
+
OpenAI's Code Interpreter plugin for ChatGPT is a revolutionary feature that allows the execution of Python code within the AI model. However, it execute code within an online sandbox and has certain limitations. In this project, we present Local Code Interpreter – which enables code execution on your local device, offering enhanced flexibility, security, and convenience.
|
| 20 |
+
|
| 21 |
+
## Key Advantages
|
| 22 |
+
|
| 23 |
+
- **Custom Environment**: Execute code in a customized environment of your choice, ensuring you have the right packages and settings.
|
| 24 |
+
|
| 25 |
+
- **Seamless Experience**: Say goodbye to file size restrictions and internet issues while uploading. With Local Code Interpreter, you're in full control.
|
| 26 |
+
|
| 27 |
+
- **GPT-3.5 Availability**: While official Code Interpreter is only available for GPT-4 model, the Local Code Interpreter offers the flexibility to switch between both GPT-3.5 and GPT-4 models.
|
| 28 |
+
|
| 29 |
+
- **Enhanced Data Security**: Keep your data more secure by running code locally, minimizing data transfer over the internet.
|
| 30 |
+
|
| 31 |
+
## Note
|
| 32 |
+
Executing AI-generated code without human review on your own device is not safe. You are responsible for taking measures to protect the security of your device and data (such as using a virtural machine) before launching this program. All consequences caused by using this program shall be borne by youself.
|
| 33 |
+
|
| 34 |
+
## Usage
|
| 35 |
+
|
| 36 |
+
### Getting Started
|
| 37 |
+
|
| 38 |
+
1. Clone this repository to your local device
|
| 39 |
+
```shell
|
| 40 |
+
git clone https://github.com/MrGreyfun/Local-Code-Interpreter.git
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
2. Install the necessary dependencies. The program has been tested on Windows 10 and CentOS Linux 7.8, with Python 3.9.16. Required packages include:
|
| 44 |
+
```text
|
| 45 |
+
Jupyter Notebook 6.5.4
|
| 46 |
+
gradio 3.39.0
|
| 47 |
+
openai 0.27.8
|
| 48 |
+
```
|
| 49 |
+
Other system or package version may also work.
|
| 50 |
+
### Configuration
|
| 51 |
+
|
| 52 |
+
1. Create a `config.json` file in the `src` directory, following the examples provided in the `config_example` directory.
|
| 53 |
+
|
| 54 |
+
2. Configure your API key in the `config.json` file.
|
| 55 |
+
|
| 56 |
+
Please Note:
|
| 57 |
+
1. **Set the `model_name` Correctly**
|
| 58 |
+
This program relies on the function calling capability of two specific models:
|
| 59 |
+
- `gpt-3.5-turbo-0613`
|
| 60 |
+
- `gpt-4-0613`
|
| 61 |
+
|
| 62 |
+
Older versions of the models will not work.
|
| 63 |
+
|
| 64 |
+
For Azure OpenAI service users:
|
| 65 |
+
- Set the `model_name` as your deployment name.
|
| 66 |
+
- Confirm that the deployed model corresponds to the `0613` version.
|
| 67 |
+
|
| 68 |
+
2. **API Version Settings**
|
| 69 |
+
If you're using Azure OpenAI service, set the `API_VERSION` to `2023-07-01-preview` in the `config.json` file. Note that other API versions do not support the necessary function calls for this program.
|
| 70 |
+
|
| 71 |
+
3. **Alternate API Key Handling**
|
| 72 |
+
If you prefer not to store your API key in the `config.json` file, you can opt for an alternate approach:
|
| 73 |
+
- Leave the `API_KEY` field in `config.json` as an empty string:
|
| 74 |
+
```json
|
| 75 |
+
"API_KEY": ""
|
| 76 |
+
```
|
| 77 |
+
- Set the environment variable `OPENAI_API_KEY` with your API key before running the program:
|
| 78 |
+
- On Windows:
|
| 79 |
+
```shell
|
| 80 |
+
set OPENAI_API_KEY=<YOUR-API-KEY>
|
| 81 |
+
```
|
| 82 |
+
- On Linux:
|
| 83 |
+
```shell
|
| 84 |
+
export OPENAI_API_KEY=<YOUR-API-KEY>
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
## Getting Started
|
| 88 |
+
|
| 89 |
+
1. Navigate to the `src` directory.
|
| 90 |
+
|
| 91 |
+
2. Run the command:
|
| 92 |
+
```shell
|
| 93 |
+
python web_ui.py
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
3. Access the generated link in your browser to start using the Local Code Interpreter.
|
| 97 |
+
|
| 98 |
+
## Example
|
| 99 |
+
|
| 100 |
+
Imagine uploading a data file and requesting the model to perform linear regression and visualize the data. See how Local Code Interpreter provides a seamless experience:
|
| 101 |
+
|
| 102 |
+
1. Upload the data and request linear regression:
|
| 103 |
+

|
| 104 |
+
|
| 105 |
+
2. Encounter an error in the generated code:
|
| 106 |
+

|
| 107 |
+
|
| 108 |
+
3. ChatGPT automatically checks the data structure and fixes the bug:
|
| 109 |
+

|
| 110 |
+
|
| 111 |
+
4. The corrected code runs successfully:
|
| 112 |
+

|
| 113 |
+
|
| 114 |
+
5. The final result meets your requirements:
|
| 115 |
+

|
| 116 |
+

|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
|
app.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from response_parser import *
|
| 2 |
+
import gradio as gr
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def initialization(state_dict: Dict) -> None:
|
| 6 |
+
if not os.path.exists('cache'):
|
| 7 |
+
os.mkdir('cache')
|
| 8 |
+
if state_dict["bot_backend"] is None:
|
| 9 |
+
state_dict["bot_backend"] = BotBackend()
|
| 10 |
+
if 'OPENAI_API_KEY' in os.environ:
|
| 11 |
+
del os.environ['OPENAI_API_KEY']
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def get_bot_backend(state_dict: Dict) -> BotBackend:
|
| 15 |
+
return state_dict["bot_backend"]
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def switch_to_gpt4(state_dict: Dict, whether_switch: bool) -> None:
|
| 19 |
+
bot_backend = get_bot_backend(state_dict)
|
| 20 |
+
if whether_switch:
|
| 21 |
+
bot_backend.update_gpt_model_choice("GPT-4")
|
| 22 |
+
else:
|
| 23 |
+
bot_backend.update_gpt_model_choice("GPT-3.5")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def add_text(state_dict: Dict, history: List, text: str) -> Tuple[List, Dict]:
|
| 27 |
+
bot_backend = get_bot_backend(state_dict)
|
| 28 |
+
bot_backend.add_text_message(user_text=text)
|
| 29 |
+
|
| 30 |
+
history = history + [(text, None)]
|
| 31 |
+
|
| 32 |
+
return history, gr.update(value="", interactive=False)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def add_file(state_dict: Dict, history: List, file) -> List:
|
| 36 |
+
bot_backend = get_bot_backend(state_dict)
|
| 37 |
+
path = file.name
|
| 38 |
+
filename = os.path.basename(path)
|
| 39 |
+
|
| 40 |
+
bot_msg = [f'📁[{filename}]', None]
|
| 41 |
+
history.append(bot_msg)
|
| 42 |
+
|
| 43 |
+
bot_backend.add_file_message(path=path, bot_msg=bot_msg)
|
| 44 |
+
|
| 45 |
+
return history
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def undo_upload_file(state_dict: Dict, history: List) -> Tuple[List, Dict]:
|
| 49 |
+
bot_backend = get_bot_backend(state_dict)
|
| 50 |
+
bot_msg = bot_backend.revoke_file()
|
| 51 |
+
|
| 52 |
+
if bot_msg is None:
|
| 53 |
+
return history, gr.Button.update(interactive=False)
|
| 54 |
+
|
| 55 |
+
else:
|
| 56 |
+
assert history[-1] == bot_msg
|
| 57 |
+
del history[-1]
|
| 58 |
+
if bot_backend.revocable_files:
|
| 59 |
+
return history, gr.Button.update(interactive=True)
|
| 60 |
+
else:
|
| 61 |
+
return history, gr.Button.update(interactive=False)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def refresh_file_display(state_dict: Dict) -> List[str]:
|
| 65 |
+
bot_backend = get_bot_backend(state_dict)
|
| 66 |
+
work_dir = bot_backend.jupyter_work_dir
|
| 67 |
+
filenames = os.listdir(work_dir)
|
| 68 |
+
paths = []
|
| 69 |
+
for filename in filenames:
|
| 70 |
+
paths.append(
|
| 71 |
+
os.path.join(work_dir, filename)
|
| 72 |
+
)
|
| 73 |
+
return paths
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def restart_ui(history: List) -> Tuple[List, Dict, Dict, Dict, Dict]:
|
| 77 |
+
history.clear()
|
| 78 |
+
return (
|
| 79 |
+
history,
|
| 80 |
+
gr.Textbox.update(value="", interactive=False),
|
| 81 |
+
gr.Button.update(interactive=False),
|
| 82 |
+
gr.Button.update(interactive=False),
|
| 83 |
+
gr.Button.update(interactive=False)
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def restart_bot_backend(state_dict: Dict) -> None:
|
| 88 |
+
bot_backend = get_bot_backend(state_dict)
|
| 89 |
+
bot_backend.restart()
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def bot(state_dict: Dict, history: List) -> List:
|
| 93 |
+
bot_backend = get_bot_backend(state_dict)
|
| 94 |
+
|
| 95 |
+
while bot_backend.finish_reason in ('new_input', 'function_call'):
|
| 96 |
+
if history[-1][0] is None:
|
| 97 |
+
history.append(
|
| 98 |
+
[None, ""]
|
| 99 |
+
)
|
| 100 |
+
else:
|
| 101 |
+
history[-1][1] = ""
|
| 102 |
+
|
| 103 |
+
response = chat_completion(bot_backend=bot_backend)
|
| 104 |
+
for chunk in response:
|
| 105 |
+
history, weather_exit = parse_response(
|
| 106 |
+
chunk=chunk,
|
| 107 |
+
history=history,
|
| 108 |
+
bot_backend=bot_backend
|
| 109 |
+
)
|
| 110 |
+
yield history
|
| 111 |
+
if weather_exit:
|
| 112 |
+
exit(-1)
|
| 113 |
+
|
| 114 |
+
yield history
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
if __name__ == '__main__':
|
| 118 |
+
config = get_config()
|
| 119 |
+
with gr.Blocks(theme=gr.themes.Base()) as block:
|
| 120 |
+
"""
|
| 121 |
+
Reference: https://www.gradio.app/guides/creating-a-chatbot-fast
|
| 122 |
+
"""
|
| 123 |
+
# UI components
|
| 124 |
+
state = gr.State(value={"bot_backend": None})
|
| 125 |
+
with gr.Tab("Chat"):
|
| 126 |
+
chatbot = gr.Chatbot([], elem_id="chatbot", label="Local Code Interpreter", height=750)
|
| 127 |
+
with gr.Row():
|
| 128 |
+
with gr.Column(scale=0.85):
|
| 129 |
+
text_box = gr.Textbox(
|
| 130 |
+
show_label=False,
|
| 131 |
+
placeholder="Enter text and press enter, or upload a file",
|
| 132 |
+
container=False
|
| 133 |
+
)
|
| 134 |
+
with gr.Column(scale=0.15, min_width=0):
|
| 135 |
+
file_upload_button = gr.UploadButton("📁", file_types=['file'])
|
| 136 |
+
with gr.Row(equal_height=True):
|
| 137 |
+
with gr.Column(scale=0.7):
|
| 138 |
+
check_box = gr.Checkbox(label="Use GPT-4", interactive=config['model']['GPT-4']['available'])
|
| 139 |
+
check_box.change(fn=switch_to_gpt4, inputs=[state, check_box])
|
| 140 |
+
with gr.Column(scale=0.15, min_width=0):
|
| 141 |
+
restart_button = gr.Button(value='🔄 Restart')
|
| 142 |
+
with gr.Column(scale=0.15, min_width=0):
|
| 143 |
+
undo_file_button = gr.Button(value="↩️Undo upload file", interactive=False)
|
| 144 |
+
with gr.Tab("Files"):
|
| 145 |
+
file_output = gr.Files()
|
| 146 |
+
gr.Markdown(
|
| 147 |
+
'''
|
| 148 |
+
<center>
|
| 149 |
+
<a href="https://github.com/MrGreyfun/Local-Code-Interpreter">
|
| 150 |
+
<img src="file=github-log.png" width="2.5%">
|
| 151 |
+
<br>
|
| 152 |
+
Open source on GitHub
|
| 153 |
+
</a>
|
| 154 |
+
</center>
|
| 155 |
+
'''
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
# Components function binding
|
| 160 |
+
txt_msg = text_box.submit(add_text, [state, chatbot, text_box], [chatbot, text_box], queue=False).then(
|
| 161 |
+
bot, [state, chatbot], chatbot
|
| 162 |
+
)
|
| 163 |
+
txt_msg.then(fn=refresh_file_display, inputs=[state], outputs=[file_output])
|
| 164 |
+
txt_msg.then(lambda: gr.update(interactive=True), None, [text_box], queue=False)
|
| 165 |
+
txt_msg.then(lambda: gr.Button.update(interactive=False), None, [undo_file_button], queue=False)
|
| 166 |
+
|
| 167 |
+
file_msg = file_upload_button.upload(
|
| 168 |
+
add_file, [state, chatbot, file_upload_button], [chatbot], queue=False
|
| 169 |
+
).then(
|
| 170 |
+
bot, [state, chatbot], chatbot
|
| 171 |
+
)
|
| 172 |
+
file_msg.then(lambda: gr.Button.update(interactive=True), None, [undo_file_button], queue=False)
|
| 173 |
+
file_msg.then(fn=refresh_file_display, inputs=[state], outputs=[file_output])
|
| 174 |
+
|
| 175 |
+
undo_file_button.click(
|
| 176 |
+
fn=undo_upload_file, inputs=[state, chatbot], outputs=[chatbot, undo_file_button]
|
| 177 |
+
).then(
|
| 178 |
+
fn=refresh_file_display, inputs=[state], outputs=[file_output]
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
restart_button.click(
|
| 182 |
+
fn=restart_ui, inputs=[chatbot],
|
| 183 |
+
outputs=[chatbot, text_box, restart_button, file_upload_button, undo_file_button]
|
| 184 |
+
).then(
|
| 185 |
+
fn=restart_bot_backend, inputs=[state], queue=False
|
| 186 |
+
).then(
|
| 187 |
+
fn=refresh_file_display, inputs=[state], outputs=[file_output]
|
| 188 |
+
).then(
|
| 189 |
+
fn=lambda: (gr.Textbox.update(interactive=True), gr.Button.update(interactive=True),
|
| 190 |
+
gr.Button.update(interactive=True)),
|
| 191 |
+
inputs=None, outputs=[text_box, restart_button, file_upload_button], queue=False
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
block.load(fn=initialization, inputs=[state])
|
| 195 |
+
|
| 196 |
+
block.queue()
|
| 197 |
+
block.launch(inbrowser=True)
|
bot_backend.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import openai
|
| 3 |
+
import os
|
| 4 |
+
import copy
|
| 5 |
+
import shutil
|
| 6 |
+
from jupyter_backend import *
|
| 7 |
+
from typing import *
|
| 8 |
+
|
| 9 |
+
functions = [
|
| 10 |
+
{
|
| 11 |
+
"name": "execute_code",
|
| 12 |
+
"description": "This function allows you to execute Python code and retrieve the terminal output. If the code "
|
| 13 |
+
"generates image output, the function will return the text '[image]'. The code is sent to a "
|
| 14 |
+
"Jupyter kernel for execution. The kernel will remain active after execution, retaining all "
|
| 15 |
+
"variables in memory.",
|
| 16 |
+
"parameters": {
|
| 17 |
+
"type": "object",
|
| 18 |
+
"properties": {
|
| 19 |
+
"code": {
|
| 20 |
+
"type": "string",
|
| 21 |
+
"description": "The code text"
|
| 22 |
+
}
|
| 23 |
+
},
|
| 24 |
+
"required": ["code"],
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
system_msg = '''You are an AI code interpreter.
|
| 30 |
+
Your goal is to help users do a variety of jobs by executing Python code.
|
| 31 |
+
|
| 32 |
+
You should:
|
| 33 |
+
1. Comprehend the user's requirements carefully & to the letter.
|
| 34 |
+
2. Give a brief description for what you plan to do & call the execute_code function to run code
|
| 35 |
+
3. Provide results analysis based on the execution output.
|
| 36 |
+
4. If error occurred, try to fix it.
|
| 37 |
+
|
| 38 |
+
Note: If the user uploads a file, you will receive a system message "User uploaded a file: filename". Use the filename as the path in the code. '''
|
| 39 |
+
|
| 40 |
+
with open('config.json') as f:
|
| 41 |
+
config = json.load(f)
|
| 42 |
+
|
| 43 |
+
if not config['API_KEY']:
|
| 44 |
+
config['API_KEY'] = os.getenv('OPENAI_API_KEY')
|
| 45 |
+
os.unsetenv('OPENAI_API_KEY')
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def get_config():
|
| 49 |
+
return config
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def config_openai_api(api_type, api_base, api_version, api_key):
|
| 53 |
+
openai.api_type = api_type
|
| 54 |
+
openai.api_base = api_base
|
| 55 |
+
openai.api_version = api_version
|
| 56 |
+
openai.api_key = api_key
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class GPTResponseLog:
|
| 60 |
+
def __init__(self):
|
| 61 |
+
self.assistant_role_name = ''
|
| 62 |
+
self.content = ''
|
| 63 |
+
self.function_name = None
|
| 64 |
+
self.function_args_str = ''
|
| 65 |
+
self.display_code_block = ''
|
| 66 |
+
self.finish_reason = 'stop'
|
| 67 |
+
self.bot_history = None
|
| 68 |
+
|
| 69 |
+
def reset_gpt_response_log_values(self, exclude=None):
|
| 70 |
+
if exclude is None:
|
| 71 |
+
exclude = []
|
| 72 |
+
|
| 73 |
+
attributes = {'assistant_role_name': '',
|
| 74 |
+
'content': '',
|
| 75 |
+
'function_name': None,
|
| 76 |
+
'function_args_str': '',
|
| 77 |
+
'display_code_block': '',
|
| 78 |
+
'finish_reason': 'stop',
|
| 79 |
+
'bot_history': None}
|
| 80 |
+
|
| 81 |
+
for attr_name in exclude:
|
| 82 |
+
del attributes[attr_name]
|
| 83 |
+
for attr_name, value in attributes.items():
|
| 84 |
+
setattr(self, attr_name, value)
|
| 85 |
+
|
| 86 |
+
def set_assistant_role_name(self, assistant_role_name: str):
|
| 87 |
+
self.assistant_role_name = assistant_role_name
|
| 88 |
+
|
| 89 |
+
def add_content(self, content: str):
|
| 90 |
+
self.content += content
|
| 91 |
+
|
| 92 |
+
def set_function_name(self, function_name: str):
|
| 93 |
+
self.function_name = function_name
|
| 94 |
+
|
| 95 |
+
def copy_current_bot_history(self, bot_history: List):
|
| 96 |
+
self.bot_history = copy.deepcopy(bot_history)
|
| 97 |
+
|
| 98 |
+
def add_function_args_str(self, function_args_str: str):
|
| 99 |
+
self.function_args_str += function_args_str
|
| 100 |
+
|
| 101 |
+
def update_display_code_block(self, display_code_block):
|
| 102 |
+
self.display_code_block = display_code_block
|
| 103 |
+
|
| 104 |
+
def update_finish_reason(self, finish_reason: str):
|
| 105 |
+
self.finish_reason = finish_reason
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class BotBackend(GPTResponseLog):
|
| 109 |
+
def __init__(self):
|
| 110 |
+
super().__init__()
|
| 111 |
+
self.unique_id = hash(id(self))
|
| 112 |
+
self.jupyter_work_dir = f'cache/work_dir_{self.unique_id}'
|
| 113 |
+
self.jupyter_kernel = JupyterKernel(work_dir=self.jupyter_work_dir)
|
| 114 |
+
self.gpt_model_choice = "GPT-3.5"
|
| 115 |
+
self.revocable_files = []
|
| 116 |
+
self._init_conversation()
|
| 117 |
+
self._init_api_config()
|
| 118 |
+
self._init_kwargs_for_chat_completion()
|
| 119 |
+
|
| 120 |
+
def _init_conversation(self):
|
| 121 |
+
first_system_msg = {'role': 'system', 'content': system_msg}
|
| 122 |
+
if hasattr(self, 'conversation'):
|
| 123 |
+
self.conversation.clear()
|
| 124 |
+
self.conversation.append(first_system_msg)
|
| 125 |
+
else:
|
| 126 |
+
self.conversation: List[Dict] = [first_system_msg]
|
| 127 |
+
|
| 128 |
+
def _init_api_config(self):
|
| 129 |
+
self.config = get_config()
|
| 130 |
+
api_type = self.config['API_TYPE']
|
| 131 |
+
api_base = self.config['API_base']
|
| 132 |
+
api_version = self.config['API_VERSION']
|
| 133 |
+
api_key = config['API_KEY']
|
| 134 |
+
config_openai_api(api_type, api_base, api_version, api_key)
|
| 135 |
+
|
| 136 |
+
def _init_kwargs_for_chat_completion(self):
|
| 137 |
+
self.kwargs_for_chat_completion = {
|
| 138 |
+
'stream': True,
|
| 139 |
+
'messages': self.conversation,
|
| 140 |
+
'functions': functions,
|
| 141 |
+
'function_call': 'auto'
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
model_name = self.config['model'][self.gpt_model_choice]['model_name']
|
| 145 |
+
|
| 146 |
+
if self.config['API_TYPE'] == 'azure':
|
| 147 |
+
self.kwargs_for_chat_completion['engine'] = model_name
|
| 148 |
+
else:
|
| 149 |
+
self.kwargs_for_chat_completion['model'] = model_name
|
| 150 |
+
|
| 151 |
+
def _clear_all_files_in_work_dir(self):
|
| 152 |
+
for filename in os.listdir(self.jupyter_work_dir):
|
| 153 |
+
os.remove(
|
| 154 |
+
os.path.join(self.jupyter_work_dir, filename)
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
def add_gpt_response_content_message(self):
|
| 158 |
+
self.conversation.append(
|
| 159 |
+
{'role': self.assistant_role_name, 'content': self.content}
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
def add_text_message(self, user_text):
|
| 163 |
+
self.conversation.append(
|
| 164 |
+
{'role': 'user', 'content': user_text}
|
| 165 |
+
)
|
| 166 |
+
self.revocable_files.clear()
|
| 167 |
+
self.update_finish_reason(finish_reason='new_input')
|
| 168 |
+
|
| 169 |
+
def add_file_message(self, path, bot_msg):
|
| 170 |
+
filename = os.path.basename(path)
|
| 171 |
+
work_dir = self.jupyter_work_dir
|
| 172 |
+
|
| 173 |
+
shutil.copy(path, work_dir)
|
| 174 |
+
|
| 175 |
+
gpt_msg = {'role': 'system', 'content': f'User uploaded a file: {filename}'}
|
| 176 |
+
self.conversation.append(gpt_msg)
|
| 177 |
+
self.revocable_files.append(
|
| 178 |
+
{
|
| 179 |
+
'bot_msg': bot_msg,
|
| 180 |
+
'gpt_msg': gpt_msg,
|
| 181 |
+
'path': os.path.join(work_dir, filename)
|
| 182 |
+
}
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
def add_function_call_response_message(self, function_response: str, save_tokens=True):
|
| 186 |
+
self.conversation.append(
|
| 187 |
+
{
|
| 188 |
+
"role": self.assistant_role_name,
|
| 189 |
+
"name": self.function_name,
|
| 190 |
+
"content": self.function_args_str
|
| 191 |
+
}
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
if save_tokens and len(function_response) > 500:
|
| 195 |
+
function_response = f'{function_response[:200]}\n[Output too much, the middle part output is omitted]\n ' \
|
| 196 |
+
f'End part of output:\n{function_response[-200:]}'
|
| 197 |
+
self.conversation.append(
|
| 198 |
+
{
|
| 199 |
+
"role": "function",
|
| 200 |
+
"name": self.function_name,
|
| 201 |
+
"content": function_response,
|
| 202 |
+
}
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
def revoke_file(self):
|
| 206 |
+
if self.revocable_files:
|
| 207 |
+
file = self.revocable_files[-1]
|
| 208 |
+
bot_msg = file['bot_msg']
|
| 209 |
+
gpt_msg = file['gpt_msg']
|
| 210 |
+
path = file['path']
|
| 211 |
+
|
| 212 |
+
assert self.conversation[-1] is gpt_msg
|
| 213 |
+
del self.conversation[-1]
|
| 214 |
+
|
| 215 |
+
os.remove(path)
|
| 216 |
+
|
| 217 |
+
del self.revocable_files[-1]
|
| 218 |
+
|
| 219 |
+
return bot_msg
|
| 220 |
+
else:
|
| 221 |
+
return None
|
| 222 |
+
|
| 223 |
+
def update_gpt_model_choice(self, model_choice):
|
| 224 |
+
self.gpt_model_choice = model_choice
|
| 225 |
+
self._init_kwargs_for_chat_completion()
|
| 226 |
+
|
| 227 |
+
def restart(self):
|
| 228 |
+
self._clear_all_files_in_work_dir()
|
| 229 |
+
self.revocable_files.clear()
|
| 230 |
+
self._init_conversation()
|
| 231 |
+
self.reset_gpt_response_log_values()
|
| 232 |
+
self.jupyter_kernel.restart_jupyter_kernel()
|
config.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"API_TYPE": "open_ai",
|
| 3 |
+
"API_base": "https://api.nova-oss.com/v1",
|
| 4 |
+
"API_VERSION": null,
|
| 5 |
+
"API_KEY": "",
|
| 6 |
+
"model": {
|
| 7 |
+
"GPT-3.5": {
|
| 8 |
+
"model_name": "gpt-3.5-turbo",
|
| 9 |
+
"available": true
|
| 10 |
+
},
|
| 11 |
+
"GPT-4": {
|
| 12 |
+
"model_name": "gpt-4-0613",
|
| 13 |
+
"available": true
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
}
|
config.json.example
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"API_TYPE": "open_ai",
|
| 3 |
+
"API_base": "https://api.chatanywhere.cn/v1",
|
| 4 |
+
"API_VERSION": null,
|
| 5 |
+
"API_KEY": "",
|
| 6 |
+
"model": {
|
| 7 |
+
"GPT-3.5": {
|
| 8 |
+
"model_name": "gpt-3.5-turbo",
|
| 9 |
+
"available": true
|
| 10 |
+
},
|
| 11 |
+
"GPT-4": {
|
| 12 |
+
"model_name": "",
|
| 13 |
+
"available": false
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
}
|
example_img/1.jpg
ADDED
|
example_img/2.jpg
ADDED
|
example_img/3.jpg
ADDED
|
example_img/4.jpg
ADDED
|
example_img/5.jpg
ADDED
|
example_img/6.jpg
ADDED
|
functional.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from bot_backend import *
|
| 2 |
+
import base64
|
| 3 |
+
import time
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def chat_completion(bot_backend: BotBackend):
|
| 7 |
+
model_choice = bot_backend.gpt_model_choice
|
| 8 |
+
config = bot_backend.config
|
| 9 |
+
kwargs_for_chat_completion = bot_backend.kwargs_for_chat_completion
|
| 10 |
+
|
| 11 |
+
assert config['model'][model_choice]['available'], f"{model_choice} is not available for you API key"
|
| 12 |
+
|
| 13 |
+
response = openai.ChatCompletion.create(**kwargs_for_chat_completion)
|
| 14 |
+
return response
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def add_function_response_to_bot_history(content_to_display, history, unique_id):
|
| 18 |
+
images, text = [], []
|
| 19 |
+
|
| 20 |
+
# terminal output
|
| 21 |
+
error_occurred = False
|
| 22 |
+
for mark, out_str in content_to_display:
|
| 23 |
+
if mark in ('stdout', 'execute_result_text', 'display_text'):
|
| 24 |
+
text.append(out_str)
|
| 25 |
+
elif mark in ('execute_result_png', 'execute_result_jpeg', 'display_png', 'display_jpeg'):
|
| 26 |
+
if 'png' in mark:
|
| 27 |
+
images.append(('png', out_str))
|
| 28 |
+
else:
|
| 29 |
+
images.append(('jpg', out_str))
|
| 30 |
+
elif mark == 'error':
|
| 31 |
+
text.append(delete_color_control_char(out_str))
|
| 32 |
+
error_occurred = True
|
| 33 |
+
text = '\n'.join(text).strip('\n')
|
| 34 |
+
if error_occurred:
|
| 35 |
+
history.append([None, f'❌Terminal output:\n```shell\n\n{text}\n```'])
|
| 36 |
+
else:
|
| 37 |
+
history.append([None, f'✔️Terminal output:\n```shell\n{text}\n```'])
|
| 38 |
+
|
| 39 |
+
# image output
|
| 40 |
+
for filetype, img in images:
|
| 41 |
+
image_bytes = base64.b64decode(img)
|
| 42 |
+
temp_path = f'cache/temp_{unique_id}'
|
| 43 |
+
if not os.path.exists(temp_path):
|
| 44 |
+
os.mkdir(temp_path)
|
| 45 |
+
path = f'{temp_path}/{hash(time.time())}.{filetype}'
|
| 46 |
+
with open(path, 'wb') as f:
|
| 47 |
+
f.write(image_bytes)
|
| 48 |
+
history.append(
|
| 49 |
+
[
|
| 50 |
+
None,
|
| 51 |
+
f'<img src=\"file={path}\" style=\'width: 600px; max-width:none; max-height:none\'>'
|
| 52 |
+
]
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def parse_json(function_args: str, finished: bool):
|
| 57 |
+
"""
|
| 58 |
+
GPT may generate non-standard JSON format string, which contains '\n' in string value, leading to error when using
|
| 59 |
+
`json.loads()`.
|
| 60 |
+
Here we implement a parser to extract code directly from non-standard JSON string.
|
| 61 |
+
:return: code string if successfully parsed otherwise None
|
| 62 |
+
"""
|
| 63 |
+
parser_log = {
|
| 64 |
+
'met_begin_{': False,
|
| 65 |
+
'begin_"code"': False,
|
| 66 |
+
'end_"code"': False,
|
| 67 |
+
'met_:': False,
|
| 68 |
+
'met_end_}': False,
|
| 69 |
+
'met_end_code_"': False,
|
| 70 |
+
"code_begin_index": 0,
|
| 71 |
+
"code_end_index": 0
|
| 72 |
+
}
|
| 73 |
+
try:
|
| 74 |
+
for index, char in enumerate(function_args):
|
| 75 |
+
if char == '{':
|
| 76 |
+
parser_log['met_begin_{'] = True
|
| 77 |
+
elif parser_log['met_begin_{'] and char == '"':
|
| 78 |
+
if parser_log['met_:']:
|
| 79 |
+
if finished:
|
| 80 |
+
parser_log['code_begin_index'] = index + 1
|
| 81 |
+
break
|
| 82 |
+
else:
|
| 83 |
+
if index + 1 == len(function_args):
|
| 84 |
+
return ''
|
| 85 |
+
else:
|
| 86 |
+
temp_code_str = function_args[index + 1:]
|
| 87 |
+
if '\n' in temp_code_str:
|
| 88 |
+
return temp_code_str.strip('\n')
|
| 89 |
+
else:
|
| 90 |
+
return json.loads(function_args + '"}')['code']
|
| 91 |
+
elif parser_log['begin_"code"']:
|
| 92 |
+
parser_log['end_"code"'] = True
|
| 93 |
+
else:
|
| 94 |
+
parser_log['begin_"code"'] = True
|
| 95 |
+
elif parser_log['end_"code"'] and char == ':':
|
| 96 |
+
parser_log['met_:'] = True
|
| 97 |
+
else:
|
| 98 |
+
continue
|
| 99 |
+
if finished:
|
| 100 |
+
for index, char in enumerate(function_args[::-1]):
|
| 101 |
+
back_index = -1 - index
|
| 102 |
+
if char == '}':
|
| 103 |
+
parser_log['met_end_}'] = True
|
| 104 |
+
elif parser_log['met_end_}'] and char == '"':
|
| 105 |
+
parser_log['code_end_index'] = back_index - 1
|
| 106 |
+
break
|
| 107 |
+
else:
|
| 108 |
+
continue
|
| 109 |
+
code_str = function_args[parser_log['code_begin_index']: parser_log['code_end_index'] + 1]
|
| 110 |
+
if '\n' in code_str:
|
| 111 |
+
return code_str.strip('\n')
|
| 112 |
+
else:
|
| 113 |
+
return json.loads(function_args)['code']
|
| 114 |
+
|
| 115 |
+
except Exception as e:
|
| 116 |
+
return None
|
gitattributes.txt
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
github-log.png
ADDED
|
jupyter_backend.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import jupyter_client
|
| 2 |
+
import re
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def delete_color_control_char(string):
|
| 6 |
+
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
|
| 7 |
+
return ansi_escape.sub('', string)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class JupyterKernel:
|
| 11 |
+
def __init__(self, work_dir):
|
| 12 |
+
self.kernel_manager, self.kernel_client = jupyter_client.manager.start_new_kernel(kernel_name='python3')
|
| 13 |
+
self.work_dir = work_dir
|
| 14 |
+
self._create_work_dir()
|
| 15 |
+
self.available_functions = {
|
| 16 |
+
'execute_code': self.execute_code,
|
| 17 |
+
'python': self.execute_code
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
def execute_code_(self, code):
|
| 21 |
+
msg_id = self.kernel_client.execute(code)
|
| 22 |
+
|
| 23 |
+
# Get the output of the code
|
| 24 |
+
iopub_msg = self.kernel_client.get_iopub_msg()
|
| 25 |
+
|
| 26 |
+
all_output = []
|
| 27 |
+
while True:
|
| 28 |
+
if iopub_msg['msg_type'] == 'stream':
|
| 29 |
+
if iopub_msg['content'].get('name') == 'stdout':
|
| 30 |
+
output = iopub_msg['content']['text']
|
| 31 |
+
all_output.append(('stdout', output))
|
| 32 |
+
iopub_msg = self.kernel_client.get_iopub_msg()
|
| 33 |
+
elif iopub_msg['msg_type'] == 'execute_result':
|
| 34 |
+
if 'data' in iopub_msg['content']:
|
| 35 |
+
if 'text/plain' in iopub_msg['content']['data']:
|
| 36 |
+
output = iopub_msg['content']['data']['text/plain']
|
| 37 |
+
all_output.append(('execute_result_text', output))
|
| 38 |
+
if 'text/html' in iopub_msg['content']['data']:
|
| 39 |
+
output = iopub_msg['content']['data']['text/html']
|
| 40 |
+
all_output.append(('execute_result_html', output))
|
| 41 |
+
if 'image/png' in iopub_msg['content']['data']:
|
| 42 |
+
output = iopub_msg['content']['data']['image/png']
|
| 43 |
+
all_output.append(('execute_result_png', output))
|
| 44 |
+
if 'image/jpeg' in iopub_msg['content']['data']:
|
| 45 |
+
output = iopub_msg['content']['data']['image/jpeg']
|
| 46 |
+
all_output.append(('execute_result_jpeg', output))
|
| 47 |
+
iopub_msg = self.kernel_client.get_iopub_msg()
|
| 48 |
+
elif iopub_msg['msg_type'] == 'display_data':
|
| 49 |
+
if 'data' in iopub_msg['content']:
|
| 50 |
+
if 'text/plain' in iopub_msg['content']['data']:
|
| 51 |
+
output = iopub_msg['content']['data']['text/plain']
|
| 52 |
+
all_output.append(('display_text', output))
|
| 53 |
+
if 'text/html' in iopub_msg['content']['data']:
|
| 54 |
+
output = iopub_msg['content']['data']['text/html']
|
| 55 |
+
all_output.append(('display_html', output))
|
| 56 |
+
if 'image/png' in iopub_msg['content']['data']:
|
| 57 |
+
output = iopub_msg['content']['data']['image/png']
|
| 58 |
+
all_output.append(('display_png', output))
|
| 59 |
+
if 'image/jpeg' in iopub_msg['content']['data']:
|
| 60 |
+
output = iopub_msg['content']['data']['image/jpeg']
|
| 61 |
+
all_output.append(('display_jpeg', output))
|
| 62 |
+
iopub_msg = self.kernel_client.get_iopub_msg()
|
| 63 |
+
elif iopub_msg['msg_type'] == 'error':
|
| 64 |
+
if 'traceback' in iopub_msg['content']:
|
| 65 |
+
output = '\n'.join(iopub_msg['content']['traceback'])
|
| 66 |
+
all_output.append(('error', output))
|
| 67 |
+
iopub_msg = self.kernel_client.get_iopub_msg()
|
| 68 |
+
elif iopub_msg['msg_type'] == 'status' and iopub_msg['content'].get('execution_state') == 'idle':
|
| 69 |
+
break
|
| 70 |
+
else:
|
| 71 |
+
iopub_msg = self.kernel_client.get_iopub_msg()
|
| 72 |
+
|
| 73 |
+
return all_output
|
| 74 |
+
|
| 75 |
+
def execute_code(self, code):
|
| 76 |
+
text_to_gpt = []
|
| 77 |
+
content_to_display = self.execute_code_(code)
|
| 78 |
+
for mark, out_str in content_to_display:
|
| 79 |
+
if mark in ('stdout', 'execute_result_text', 'display_text'):
|
| 80 |
+
text_to_gpt.append(out_str)
|
| 81 |
+
elif mark in ('execute_result_png', 'execute_result_jpeg', 'display_png', 'display_jpeg'):
|
| 82 |
+
text_to_gpt.append('[image]')
|
| 83 |
+
elif mark == 'error':
|
| 84 |
+
text_to_gpt.append(delete_color_control_char(out_str))
|
| 85 |
+
|
| 86 |
+
return '\n'.join(text_to_gpt), content_to_display
|
| 87 |
+
|
| 88 |
+
def _create_work_dir(self):
|
| 89 |
+
# set work dir in jupyter environment
|
| 90 |
+
init_code = f"import os\n" \
|
| 91 |
+
f"if not os.path.exists('{self.work_dir}'):\n" \
|
| 92 |
+
f" os.mkdir('{self.work_dir}')\n" \
|
| 93 |
+
f"os.chdir('{self.work_dir}')\n" \
|
| 94 |
+
f"del os"
|
| 95 |
+
self.execute_code_(init_code)
|
| 96 |
+
|
| 97 |
+
def restart_jupyter_kernel(self):
|
| 98 |
+
self.kernel_client.shutdown()
|
| 99 |
+
self.kernel_manager, self.kernel_client = jupyter_client.manager.start_new_kernel(kernel_name='python3')
|
| 100 |
+
self._create_work_dir()
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
notebook==6.5.4
|
| 2 |
+
gradio==3.39.0
|
| 3 |
+
openai==0.27.8
|
| 4 |
+
pandas
|
| 5 |
+
numpy
|
| 6 |
+
openpyxl
|
| 7 |
+
Pillow
|
| 8 |
+
opencv-python
|
| 9 |
+
PyPDF2
|
| 10 |
+
pdfminer.six
|
| 11 |
+
sympy
|
| 12 |
+
PyMuPDF
|
response_parser.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import ABCMeta, abstractmethod
|
| 2 |
+
from functional import *
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class ChoiceStrategy(metaclass=ABCMeta):
|
| 6 |
+
def __init__(self, choice):
|
| 7 |
+
self.choice = choice
|
| 8 |
+
self.delta = choice['delta']
|
| 9 |
+
|
| 10 |
+
@abstractmethod
|
| 11 |
+
def support(self):
|
| 12 |
+
pass
|
| 13 |
+
|
| 14 |
+
@abstractmethod
|
| 15 |
+
def execute(self, bot_backend: BotBackend, history: List, whether_exit: bool):
|
| 16 |
+
pass
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class RoleChoiceStrategy(ChoiceStrategy):
|
| 20 |
+
|
| 21 |
+
def support(self):
|
| 22 |
+
return 'role' in self.delta
|
| 23 |
+
|
| 24 |
+
def execute(self, bot_backend: BotBackend, history: List, whether_exit: bool):
|
| 25 |
+
bot_backend.set_assistant_role_name(assistant_role_name=self.delta['role'])
|
| 26 |
+
return history, whether_exit
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class ContentChoiceStrategy(ChoiceStrategy):
|
| 30 |
+
def support(self):
|
| 31 |
+
return 'content' in self.delta and self.delta['content'] is not None
|
| 32 |
+
|
| 33 |
+
def execute(self, bot_backend: BotBackend, history: List, whether_exit: bool):
|
| 34 |
+
# null value of content often occur in function call:
|
| 35 |
+
# {
|
| 36 |
+
# "role": "assistant",
|
| 37 |
+
# "content": null,
|
| 38 |
+
# "function_call": {
|
| 39 |
+
# "name": "python",
|
| 40 |
+
# "arguments": ""
|
| 41 |
+
# }
|
| 42 |
+
# }
|
| 43 |
+
bot_backend.add_content(content=self.delta.get('content', ''))
|
| 44 |
+
history[-1][1] = bot_backend.content
|
| 45 |
+
return history, whether_exit
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class NameFunctionCallChoiceStrategy(ChoiceStrategy):
|
| 49 |
+
def support(self):
|
| 50 |
+
return 'function_call' in self.delta and 'name' in self.delta['function_call']
|
| 51 |
+
|
| 52 |
+
def execute(self, bot_backend: BotBackend, history: List, whether_exit: bool):
|
| 53 |
+
function_dict = bot_backend.jupyter_kernel.available_functions
|
| 54 |
+
bot_backend.set_function_name(function_name=self.delta['function_call']['name'])
|
| 55 |
+
bot_backend.copy_current_bot_history(bot_history=history)
|
| 56 |
+
if bot_backend.function_name not in function_dict:
|
| 57 |
+
history.append(
|
| 58 |
+
[
|
| 59 |
+
None,
|
| 60 |
+
f'GPT attempted to call a function that does '
|
| 61 |
+
f'not exist: {bot_backend.function_name}\n '
|
| 62 |
+
]
|
| 63 |
+
)
|
| 64 |
+
whether_exit = True
|
| 65 |
+
|
| 66 |
+
return history, whether_exit
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
class ArgumentsFunctionCallChoiceStrategy(ChoiceStrategy):
|
| 70 |
+
|
| 71 |
+
def support(self):
|
| 72 |
+
return 'function_call' in self.delta and 'arguments' in self.delta['function_call']
|
| 73 |
+
|
| 74 |
+
def execute(self, bot_backend: BotBackend, history: List, whether_exit: bool):
|
| 75 |
+
bot_backend.add_function_args_str(function_args_str=self.delta['function_call']['arguments'])
|
| 76 |
+
|
| 77 |
+
if bot_backend.function_name == 'python': # handle hallucinatory function calls
|
| 78 |
+
'''
|
| 79 |
+
In practice, we have noticed that GPT, especially GPT-3.5, may occasionally produce hallucinatory
|
| 80 |
+
function calls. These calls involve a non-existent function named `python` with arguments consisting
|
| 81 |
+
solely of raw code text (not a JSON format).
|
| 82 |
+
'''
|
| 83 |
+
temp_code_str = bot_backend.function_args_str
|
| 84 |
+
bot_backend.update_display_code_block(
|
| 85 |
+
display_code_block="\n🔴Working:\n```python\n{}\n```".format(temp_code_str)
|
| 86 |
+
)
|
| 87 |
+
history = copy.deepcopy(bot_backend.bot_history)
|
| 88 |
+
history[-1][1] += bot_backend.display_code_block
|
| 89 |
+
else:
|
| 90 |
+
temp_code_str = parse_json(function_args=bot_backend.function_args_str, finished=False)
|
| 91 |
+
if temp_code_str is not None:
|
| 92 |
+
bot_backend.update_display_code_block(
|
| 93 |
+
display_code_block="\n🔴Working:\n```python\n{}\n```".format(
|
| 94 |
+
temp_code_str
|
| 95 |
+
)
|
| 96 |
+
)
|
| 97 |
+
history = copy.deepcopy(bot_backend.bot_history)
|
| 98 |
+
history[-1][1] += bot_backend.display_code_block
|
| 99 |
+
|
| 100 |
+
return history, whether_exit
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
class FinishReasonChoiceStrategy(ChoiceStrategy):
|
| 104 |
+
def support(self):
|
| 105 |
+
return self.choice['finish_reason'] is not None
|
| 106 |
+
|
| 107 |
+
def execute(self, bot_backend: BotBackend, history: List, whether_exit: bool):
|
| 108 |
+
function_dict = bot_backend.jupyter_kernel.available_functions
|
| 109 |
+
|
| 110 |
+
if bot_backend.content:
|
| 111 |
+
bot_backend.add_gpt_response_content_message()
|
| 112 |
+
|
| 113 |
+
bot_backend.update_finish_reason(finish_reason=self.choice['finish_reason'])
|
| 114 |
+
if bot_backend.finish_reason == 'function_call':
|
| 115 |
+
try:
|
| 116 |
+
|
| 117 |
+
code_str = self.get_code_str(bot_backend)
|
| 118 |
+
|
| 119 |
+
bot_backend.update_display_code_block(
|
| 120 |
+
display_code_block="\n🟢Working:\n```python\n{}\n```".format(code_str)
|
| 121 |
+
)
|
| 122 |
+
history = copy.deepcopy(bot_backend.bot_history)
|
| 123 |
+
history[-1][1] += bot_backend.display_code_block
|
| 124 |
+
|
| 125 |
+
# function response
|
| 126 |
+
text_to_gpt, content_to_display = function_dict[
|
| 127 |
+
bot_backend.function_name
|
| 128 |
+
](code_str)
|
| 129 |
+
|
| 130 |
+
# add function call to conversion
|
| 131 |
+
bot_backend.add_function_call_response_message(function_response=text_to_gpt, save_tokens=True)
|
| 132 |
+
|
| 133 |
+
add_function_response_to_bot_history(
|
| 134 |
+
content_to_display=content_to_display, history=history, unique_id=bot_backend.unique_id
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
except json.JSONDecodeError:
|
| 138 |
+
history.append(
|
| 139 |
+
[None, f"GPT generate wrong function args: {bot_backend.function_args_str}"]
|
| 140 |
+
)
|
| 141 |
+
whether_exit = True
|
| 142 |
+
return history, whether_exit
|
| 143 |
+
|
| 144 |
+
except Exception as e:
|
| 145 |
+
history.append([None, f'Backend error: {e}'])
|
| 146 |
+
whether_exit = True
|
| 147 |
+
return history, whether_exit
|
| 148 |
+
|
| 149 |
+
bot_backend.reset_gpt_response_log_values(exclude=['finish_reason'])
|
| 150 |
+
|
| 151 |
+
return history, whether_exit
|
| 152 |
+
|
| 153 |
+
@staticmethod
|
| 154 |
+
def get_code_str(bot_backend):
|
| 155 |
+
if bot_backend.function_name == 'python':
|
| 156 |
+
code_str = bot_backend.function_args_str
|
| 157 |
+
else:
|
| 158 |
+
code_str = parse_json(function_args=bot_backend.function_args_str, finished=True)
|
| 159 |
+
if code_str is None:
|
| 160 |
+
raise json.JSONDecodeError
|
| 161 |
+
return code_str
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
class ChoiceHandler:
|
| 165 |
+
strategies = [
|
| 166 |
+
RoleChoiceStrategy, ContentChoiceStrategy, NameFunctionCallChoiceStrategy,
|
| 167 |
+
ArgumentsFunctionCallChoiceStrategy, FinishReasonChoiceStrategy
|
| 168 |
+
]
|
| 169 |
+
|
| 170 |
+
def __init__(self, choice):
|
| 171 |
+
self.choice = choice
|
| 172 |
+
|
| 173 |
+
def handle(self, bot_backend: BotBackend, history: List, whether_exit: bool):
|
| 174 |
+
for Strategy in self.strategies:
|
| 175 |
+
strategy_instance = Strategy(choice=self.choice)
|
| 176 |
+
if not strategy_instance.support():
|
| 177 |
+
continue
|
| 178 |
+
history, whether_exit = strategy_instance.execute(
|
| 179 |
+
bot_backend=bot_backend,
|
| 180 |
+
history=history,
|
| 181 |
+
whether_exit=whether_exit
|
| 182 |
+
)
|
| 183 |
+
return history, whether_exit
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def parse_response(chunk, history, bot_backend: BotBackend):
|
| 187 |
+
"""
|
| 188 |
+
:return: history, whether_exit
|
| 189 |
+
"""
|
| 190 |
+
whether_exit = False
|
| 191 |
+
if chunk['choices']:
|
| 192 |
+
choice = chunk['choices'][0]
|
| 193 |
+
choice_handler = ChoiceHandler(choice=choice)
|
| 194 |
+
history, whether_exit = choice_handler.handle(
|
| 195 |
+
history=history,
|
| 196 |
+
bot_backend=bot_backend,
|
| 197 |
+
whether_exit=whether_exit
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
return history, whether_exit
|