Spaces:
Running
on
Zero
Running
on
Zero
update
Browse files- README.md +18 -7
- app.py +328 -46
- install_node.sh +16 -0
- packages.txt +2 -0
- postprocess.py +156 -0
- preprocess.py +256 -0
- requirements.txt +11 -1
- style.css +35 -0
README.md
CHANGED
@@ -1,13 +1,24 @@
|
|
1 |
---
|
2 |
-
title: Rubra
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
-
sdk_version: 4.
|
8 |
-
app_file: app.py
|
9 |
pinned: false
|
10 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
---
|
12 |
|
13 |
-
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
title: Rubra v0.1
|
3 |
+
emoji: 🦙
|
4 |
+
colorFrom: indigo
|
5 |
+
colorTo: pink
|
6 |
sdk: gradio
|
7 |
+
sdk_version: 4.37.1
|
|
|
8 |
pinned: false
|
9 |
license: apache-2.0
|
10 |
+
thumbnail: https://rubra.ai/
|
11 |
+
suggested_hardware: a10g-large
|
12 |
+
# preload_from_hub:
|
13 |
+
# - rubra-ai/Meta-Llama-3-8B-Instruct
|
14 |
+
# - rubra-ai/Phi-3-mini-128k-instruct
|
15 |
+
# - rubra-ai/Mistral-7B-Instruct-v0.3
|
16 |
+
# - rubra-ai/Mistral-7B-Instruct-v0.2
|
17 |
+
# - rubra-ai/gemma-1.1-2b-it
|
18 |
+
# - rubra-ai/Qwen2-7B-Instruct
|
19 |
---
|
20 |
|
21 |
+
# Rubra v0.1 - A Collection of Tool (Function) Calling LLMs
|
22 |
+
|
23 |
+
This Space demonstrates Rubra tool calling models. Please, check the original model cards for details.
|
24 |
+
|
app.py
CHANGED
@@ -1,63 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
"""
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
"""
|
7 |
-
client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
|
8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
|
10 |
-
def
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
):
|
18 |
-
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
-
|
21 |
-
|
22 |
-
messages.append({"role": "user", "content": val[0]})
|
23 |
-
if val[1]:
|
24 |
-
messages.append({"role": "assistant", "content": val[1]})
|
25 |
|
26 |
-
|
|
|
|
|
|
|
27 |
|
28 |
-
|
|
|
|
|
29 |
|
30 |
-
for
|
31 |
-
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
temperature=temperature,
|
35 |
-
|
36 |
-
|
37 |
-
|
|
|
|
|
38 |
|
39 |
-
|
40 |
-
|
|
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
"""
|
43 |
-
For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
|
44 |
-
"""
|
45 |
-
demo = gr.ChatInterface(
|
46 |
-
respond,
|
47 |
-
additional_inputs=[
|
48 |
-
gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
|
49 |
-
gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
|
50 |
-
gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
|
51 |
-
gr.Slider(
|
52 |
-
minimum=0.1,
|
53 |
-
maximum=1.0,
|
54 |
-
value=0.95,
|
55 |
-
step=0.05,
|
56 |
-
label="Top-p (nucleus sampling)",
|
57 |
-
),
|
58 |
-
],
|
59 |
-
)
|
60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
|
62 |
if __name__ == "__main__":
|
63 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
from threading import Thread
|
3 |
+
from typing import Iterator
|
4 |
+
import json
|
5 |
+
|
6 |
import gradio as gr
|
7 |
+
import spaces
|
8 |
+
import torch
|
9 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
|
10 |
+
import subprocess
|
11 |
+
import copy
|
12 |
+
|
13 |
+
import subprocess
|
14 |
+
import sys
|
15 |
+
|
16 |
+
def run_command(command):
|
17 |
+
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
18 |
+
output, error = process.communicate()
|
19 |
+
if process.returncode != 0:
|
20 |
+
print(f"Error executing command: {command}")
|
21 |
+
print(f"Error message: {error.decode('utf-8')}")
|
22 |
+
sys.exit(1)
|
23 |
+
return output.decode('utf-8')
|
24 |
+
|
25 |
+
MAX_MAX_NEW_TOKENS = 2048
|
26 |
+
DEFAULT_MAX_NEW_TOKENS = 1024
|
27 |
+
MAX_INPUT_TOKEN_LENGTH = int(os.getenv("MAX_INPUT_TOKEN_LENGTH", "8000"))
|
28 |
+
model_choices = [
|
29 |
+
"rubra-ai/Meta-Llama-3-8B-Instruct",
|
30 |
+
"rubra-ai/Qwen2-7B-Instruct",
|
31 |
+
"rubra-ai/Phi-3-mini-128k-instruct",
|
32 |
+
"rubra-ai/Mistral-7B-Instruct-v0.3",
|
33 |
+
"rubra-ai/Mistral-7B-Instruct-v0.2",
|
34 |
+
"rubra-ai/gemma-1.1-2b-it"
|
35 |
+
]
|
36 |
+
|
37 |
+
DESCRIPTION = """\
|
38 |
+
# Rubra v0.1 - Top LLMs enhanced with function (tool) calling
|
39 |
+
|
40 |
+
This is a demo of the Rubra collection of models. You can use the models for general conversation,
|
41 |
+
task completion, and function calling with the provided tools input.
|
42 |
|
43 |
"""
|
44 |
+
|
45 |
+
LICENSE = """
|
46 |
+
<p/>
|
47 |
+
|
48 |
+
---
|
49 |
+
Rubra code is licensed under the Apache License, Version 2.0 (the "License");
|
50 |
+
you may not use this file except in compliance with the License.
|
51 |
+
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
52 |
+
|
53 |
+
Unless required by applicable law or agreed to in writing, software
|
54 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
55 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
56 |
+
See the License for the specific language governing permissions and
|
57 |
+
limitations under the License.
|
58 |
+
|
59 |
+
Rubra models are licensed under the parent model's license. See the parent model card for more information.
|
60 |
"""
|
|
|
61 |
|
62 |
+
if not torch.cuda.is_available():
|
63 |
+
DESCRIPTION += "\n<p>Running on CPU 🥶 This demo does not work on CPU.</p>"
|
64 |
+
|
65 |
+
if torch.cuda.is_available():
|
66 |
+
model_id = "sanjay920/Llama-3-8b-function-calling-alpha-v1" # Default model
|
67 |
+
model = None
|
68 |
+
tokenizer = None
|
69 |
+
|
70 |
+
def load_model(model_name):
|
71 |
+
global model, tokenizer
|
72 |
+
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto", load_in_4bit=False)
|
73 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
74 |
+
tokenizer.use_default_system_prompt = False
|
75 |
+
model.generation_config.pad_token_id = tokenizer.pad_token_id
|
76 |
+
|
77 |
+
load_model(model_id) # Load the default model
|
78 |
+
|
79 |
+
def is_valid_json(tools: str) -> bool:
|
80 |
+
try:
|
81 |
+
json.loads(tools)
|
82 |
+
return True
|
83 |
+
except ValueError:
|
84 |
+
return False
|
85 |
+
|
86 |
+
def validate_tools(tools):
|
87 |
+
if tools.strip() == "" or is_valid_json(tools):
|
88 |
+
return gr.update(visible=False)
|
89 |
+
else:
|
90 |
+
return gr.update(visible=True)
|
91 |
|
92 |
+
def json_to_markdown(json_obj):
|
93 |
+
"""Convert a JSON object to a formatted markdown string."""
|
94 |
+
markdown = ""
|
95 |
+
for item in json_obj:
|
96 |
+
if item.get("type") == "text":
|
97 |
+
# For text items, just add the text content
|
98 |
+
markdown += item.get("text", "") + "\n\n"
|
99 |
+
elif item.get("type") == "function":
|
100 |
+
# For function calls, format as JSON
|
101 |
+
markdown += "```json\n"
|
102 |
+
# markdown += json.dumps(item.get("function", {}), indent=2)
|
103 |
+
markdown += json.dumps(item, indent=2)
|
104 |
+
markdown += "\n```\n\n"
|
105 |
+
return markdown.strip()
|
106 |
|
107 |
+
def user(user_message, history):
|
108 |
+
return "", history + [[user_message, None]]
|
|
|
|
|
|
|
109 |
|
110 |
+
def bot(history, system_prompt, tools, role, max_new_tokens, temperature):
|
111 |
+
user_message = history[-1][0]
|
112 |
+
if history[-1][1] is None:
|
113 |
+
history[-1][1] = "" # Ensure it's never None
|
114 |
|
115 |
+
ui_history = list(history) # Clone the history for UI updates
|
116 |
+
all_tool_outputs = [] # Store all processed outputs for final aggregation
|
117 |
+
output_accumulated = "" # To accumulate outputs before processing
|
118 |
|
119 |
+
for chunk in generate(user_message, history[:-1], system_prompt, tools, role, max_new_tokens, temperature):
|
120 |
+
history[-1][1] += chunk
|
121 |
+
print(history[-1][1])
|
122 |
+
|
123 |
+
if "endtoolcall" in history[-1][1]:
|
124 |
+
process_output = postprocess_output(history[-1][1])
|
125 |
+
print("process output:\n", process_output)
|
126 |
+
if process_output:
|
127 |
+
temp_history = copy.deepcopy(history) # Use deepcopy here
|
128 |
+
if isinstance(process_output, list) and len(process_output) > 0 and isinstance(process_output[0], dict):
|
129 |
+
markdown_output = json_to_markdown(process_output)
|
130 |
+
temp_history[-1][1] = markdown_output
|
131 |
+
else:
|
132 |
+
temp_history[-1][1] = str(process_output)
|
133 |
+
print(temp_history[-1][1])
|
134 |
+
print("--------------------------")
|
135 |
+
yield temp_history
|
136 |
+
else:
|
137 |
+
print(history[-1][1])
|
138 |
+
print("--------------------------")
|
139 |
+
yield history
|
140 |
+
else:
|
141 |
+
print(history[-1][1])
|
142 |
+
print("--------------------------")
|
143 |
+
yield history
|
144 |
+
|
145 |
+
@spaces.GPU
|
146 |
+
def generate(
|
147 |
+
message: str,
|
148 |
+
chat_history: list[tuple[str, str]],
|
149 |
+
system_prompt: str,
|
150 |
+
tools: str,
|
151 |
+
role: str,
|
152 |
+
max_new_tokens: int = 1024,
|
153 |
+
temperature: float = 0.6,
|
154 |
+
) -> Iterator[str]:
|
155 |
+
global model, tokenizer
|
156 |
+
conversation = []
|
157 |
+
if system_prompt:
|
158 |
+
conversation.append({"role": "system", "content": system_prompt})
|
159 |
+
for user, assistant in chat_history:
|
160 |
+
conversation.extend([{"role": "user", "content": user}, {"role": "assistant", "content": assistant}])
|
161 |
+
conversation.append({"role": role, "content": message})
|
162 |
+
|
163 |
+
if tools:
|
164 |
+
if not is_valid_json(tools):
|
165 |
+
yield "Invalid JSON in tools. Please correct it."
|
166 |
+
return
|
167 |
+
tools = json.loads(tools)
|
168 |
+
formatted_msgs = preprocess_input(msgs=conversation, tools=tools)
|
169 |
+
else:
|
170 |
+
formatted_msgs = conversation
|
171 |
+
|
172 |
+
input_ids = tokenizer.apply_chat_template(formatted_msgs, return_tensors="pt")
|
173 |
+
if input_ids.shape[1] > MAX_INPUT_TOKEN_LENGTH:
|
174 |
+
input_ids = input_ids[:, -MAX_INPUT_TOKEN_LENGTH:]
|
175 |
+
gr.Warning(f"Trimmed input from conversation as it was longer than {MAX_INPUT_TOKEN_LENGTH} tokens.")
|
176 |
+
input_ids = input_ids.to(model.device)
|
177 |
+
|
178 |
+
streamer = TextIteratorStreamer(tokenizer, timeout=10.0, skip_prompt=True, skip_special_tokens=True)
|
179 |
+
generate_kwargs = dict(
|
180 |
+
input_ids=input_ids,
|
181 |
+
streamer=streamer,
|
182 |
+
max_new_tokens=max_new_tokens,
|
183 |
+
do_sample=True,
|
184 |
+
top_p=0.95,
|
185 |
temperature=temperature,
|
186 |
+
num_beams=1,
|
187 |
+
repetition_penalty=1.2,
|
188 |
+
)
|
189 |
+
t = Thread(target=model.generate, kwargs=generate_kwargs)
|
190 |
+
t.start()
|
191 |
|
192 |
+
for text in streamer:
|
193 |
+
# print("Generated text:", text)
|
194 |
+
yield text
|
195 |
|
196 |
+
bot_message = """Hello! How can I assist you today? If you have any questions or need information on a specific topic, feel free to ask. I can also utilize `tools` that you input to help you better. For example:
|
197 |
+
```
|
198 |
+
[
|
199 |
+
{
|
200 |
+
"type": "function",
|
201 |
+
"function": {
|
202 |
+
"name": "get_current_weather",
|
203 |
+
"description": "Get the current weather in a given location",
|
204 |
+
"parameters": {
|
205 |
+
"type": "object",
|
206 |
+
"properties": {
|
207 |
+
"location": {
|
208 |
+
"type": "string",
|
209 |
+
"description": "Must include the city AND state, e.g. 'San Francisco, CA'"
|
210 |
+
},
|
211 |
+
"unit": {
|
212 |
+
"type": "string",
|
213 |
+
"enum":
|
214 |
+
["celsius", "fahrenheit"]
|
215 |
+
}
|
216 |
+
},
|
217 |
+
"required": ["location"]
|
218 |
+
}
|
219 |
+
}
|
220 |
+
}
|
221 |
+
]
|
222 |
+
```
|
223 |
+
|
224 |
+
You can also define `functions` (deprecated in favor of `tools` in OpenAI):
|
225 |
+
```
|
226 |
+
[
|
227 |
+
{
|
228 |
+
"name": "get_current_date",
|
229 |
+
"description": "Gets the current date at the given location. Results are in ISO 8601 date format; e.g. 2024-04-25",
|
230 |
+
"parameters": {
|
231 |
+
"type": "object",
|
232 |
+
"properties": {
|
233 |
+
"location": {
|
234 |
+
"type": "string",
|
235 |
+
"description": "The city and state to get the current date at, e.g. San Francisco, CA"
|
236 |
+
}
|
237 |
+
},
|
238 |
+
"required":["location"]
|
239 |
+
}
|
240 |
+
}
|
241 |
+
]
|
242 |
+
```
|
243 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
244 |
|
245 |
+
def create_chat_interface():
|
246 |
+
with gr.Blocks(css="style.css") as demo:
|
247 |
+
gr.Markdown(DESCRIPTION)
|
248 |
+
|
249 |
+
with gr.Row(equal_height=True, elem_id="main-row"):
|
250 |
+
with gr.Column(scale=3, min_width=500):
|
251 |
+
# Initialize the chatbot with the welcome message
|
252 |
+
chatbot = gr.Chatbot(
|
253 |
+
value=[("Hi", bot_message)],
|
254 |
+
show_copy_button=True,
|
255 |
+
elem_id="chatbot",
|
256 |
+
show_label=False,
|
257 |
+
render_markdown=True,
|
258 |
+
height="100%",
|
259 |
+
layout='bubble',
|
260 |
+
avatar_images=("human.png", "bot.png")
|
261 |
+
)
|
262 |
+
|
263 |
+
error_box = gr.Markdown(visible=False, elem_id="error-box")
|
264 |
+
|
265 |
+
with gr.Column(scale=2, min_width=300):
|
266 |
+
model_dropdown = gr.Dropdown(
|
267 |
+
choices=model_choices,
|
268 |
+
label="Select Model",
|
269 |
+
value="sanjay920/Llama-3-8b-function-calling-alpha-v1"
|
270 |
+
)
|
271 |
+
model_dropdown.change(load_model, inputs=[model_dropdown])
|
272 |
+
|
273 |
+
with gr.Accordion("Settings", open=False):
|
274 |
+
max_new_tokens = gr.Slider(
|
275 |
+
label="Max new tokens",
|
276 |
+
minimum=1,
|
277 |
+
maximum=MAX_MAX_NEW_TOKENS,
|
278 |
+
step=1,
|
279 |
+
value=DEFAULT_MAX_NEW_TOKENS,
|
280 |
+
)
|
281 |
+
temperature = gr.Slider(
|
282 |
+
label="Temperature",
|
283 |
+
minimum=0.0,
|
284 |
+
maximum=1.2,
|
285 |
+
step=0.01,
|
286 |
+
value=0.01,
|
287 |
+
)
|
288 |
+
|
289 |
+
with gr.Row():
|
290 |
+
role = gr.Dropdown(choices=["user", "observation"], value="user", label="Role", scale=4)
|
291 |
+
system_prompt = gr.Textbox(label="System Prompt", lines=1, info="Optional")
|
292 |
+
tools = gr.Textbox(label="Tools", lines=1, placeholder="Enter tools in JSON format", info="Optional")
|
293 |
+
|
294 |
+
|
295 |
+
|
296 |
+
with gr.Row():
|
297 |
+
user_input = gr.Textbox(
|
298 |
+
label="User Input",
|
299 |
+
placeholder="Type your message here...",
|
300 |
+
show_label=True,
|
301 |
+
scale=8
|
302 |
+
)
|
303 |
+
submit_btn = gr.Button("Submit", variant="primary", elem_id="submit-button")
|
304 |
+
clear_btn = gr.Button("Clear Conversation", elem_id="clear-button")
|
305 |
+
|
306 |
+
|
307 |
+
tools.change(validate_tools, tools, error_box)
|
308 |
+
|
309 |
+
submit_btn.click(
|
310 |
+
user,
|
311 |
+
[user_input, chatbot],
|
312 |
+
[user_input, chatbot],
|
313 |
+
queue=False
|
314 |
+
).then(
|
315 |
+
bot,
|
316 |
+
[chatbot, system_prompt, tools, role, max_new_tokens, temperature],
|
317 |
+
chatbot
|
318 |
+
)
|
319 |
+
|
320 |
+
clear_btn.click(lambda: ([], None), outputs=[chatbot, error_box])
|
321 |
+
|
322 |
+
gr.Markdown(LICENSE)
|
323 |
+
|
324 |
+
return demo
|
325 |
|
326 |
if __name__ == "__main__":
|
327 |
+
# Initialize npm project if package.json doesn't exist
|
328 |
+
if not os.path.exists('package.json'):
|
329 |
+
print("Initializing npm project...")
|
330 |
+
run_command("npm init -y")
|
331 |
+
|
332 |
+
# Install jsonrepair locally
|
333 |
+
print("Installing jsonrepair...")
|
334 |
+
run_command("npm install jsonrepair")
|
335 |
+
|
336 |
+
# Verify installation
|
337 |
+
print("Verifying jsonrepair installation:")
|
338 |
+
run_command("npm list jsonrepair")
|
339 |
+
|
340 |
+
# Add node_modules/.bin to PATH
|
341 |
+
os.environ['PATH'] = f"{os.path.join(os.getcwd(), 'node_modules', '.bin')}:{os.environ['PATH']}"
|
342 |
+
from preprocess import preprocess_input
|
343 |
+
from postprocess import postprocess_output
|
344 |
+
demo = create_chat_interface()
|
345 |
+
demo.queue(max_size=20).launch()
|
install_node.sh
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
# installs nvm (Node Version Manager)
|
3 |
+
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
|
4 |
+
|
5 |
+
source ~/.nvm/nvm.sh
|
6 |
+
|
7 |
+
# download and install Node.js (you may need to restart the terminal)
|
8 |
+
nvm install 20
|
9 |
+
|
10 |
+
# verifies the right Node.js version is in the environment
|
11 |
+
~/.nvm/versions/node/v20.15.0/bin/node -v # should print `v20.15.0`
|
12 |
+
|
13 |
+
# verifies the right NPM version is in the environment
|
14 |
+
~/.nvm/versions/node/v20.15.0/bin/npm -v # should print `10.7.0`
|
15 |
+
|
16 |
+
~/.nvm/versions/node/v20.15.0/bin/npm install jsonrepair -g
|
packages.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
nodejs
|
2 |
+
npm
|
postprocess.py
ADDED
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import uuid
|
3 |
+
import re
|
4 |
+
from typing import List
|
5 |
+
import subprocess
|
6 |
+
import sys
|
7 |
+
|
8 |
+
def install(package):
|
9 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
10 |
+
|
11 |
+
try:
|
12 |
+
import pythonmonkey
|
13 |
+
except ImportError:
|
14 |
+
install('pythonmonkey')
|
15 |
+
import pythonmonkey
|
16 |
+
|
17 |
+
# Your code using pythonmonkey
|
18 |
+
|
19 |
+
# Assuming jsonrepair is accessible
|
20 |
+
jsonrepair = pythonmonkey.require('jsonrepair').jsonrepair
|
21 |
+
|
22 |
+
def clean_command_string(command_str):
|
23 |
+
cleaned_command = re.sub(r'\\(?!["\\/bfnrt]|u[a-fA-F0-9]{4})', '', command_str)
|
24 |
+
cleaned_command = cleaned_command.replace('\\"', '"')
|
25 |
+
if cleaned_command.startswith('"') and cleaned_command.endswith('"'):
|
26 |
+
cleaned_command = cleaned_command[1:-1]
|
27 |
+
return cleaned_command
|
28 |
+
|
29 |
+
def parse_json_safely(json_str):
|
30 |
+
try:
|
31 |
+
return json.loads(json_str)
|
32 |
+
except json.JSONDecodeError:
|
33 |
+
try:
|
34 |
+
repaired = jsonrepair(json_str)
|
35 |
+
return json.loads(repaired)
|
36 |
+
except Exception:
|
37 |
+
return json_str
|
38 |
+
|
39 |
+
def clean_json_object(obj):
|
40 |
+
if isinstance(obj, dict):
|
41 |
+
return {k: clean_json_object(v) for k, v in obj.items()}
|
42 |
+
elif isinstance(obj, list):
|
43 |
+
return [clean_json_object(item) for item in obj]
|
44 |
+
elif isinstance(obj, str):
|
45 |
+
cleaned = clean_command_string(obj)
|
46 |
+
return parse_json_safely(cleaned) if cleaned.startswith('{') or cleaned.startswith('[') else cleaned
|
47 |
+
else:
|
48 |
+
return obj
|
49 |
+
|
50 |
+
def extract_tool_calls(output_str):
|
51 |
+
# Pattern to capture everything after 'starttoolcall' until 'endtoolcall' or end of string if 'endtoolcall' isn't present
|
52 |
+
pattern = r'starttoolcall(.*?)(?:endtoolcall|$)'
|
53 |
+
matches = [match for match in re.findall(pattern, output_str, re.DOTALL)]
|
54 |
+
return matches
|
55 |
+
|
56 |
+
def extract_tool_calls_and_text(output_str):
|
57 |
+
# Initialize an empty list to collect all segments
|
58 |
+
segments = []
|
59 |
+
|
60 |
+
# Last index processed in the string
|
61 |
+
last_end = 0
|
62 |
+
|
63 |
+
# Pattern to capture everything after 'starttoolcall' until 'endtoolcall' or end of string if 'endtoolcall' isn't present
|
64 |
+
pattern = r'(starttoolcall(.*?)(?:endtoolcall|$))'
|
65 |
+
for match in re.finditer(pattern, output_str, re.DOTALL):
|
66 |
+
start, end = match.span(1)
|
67 |
+
|
68 |
+
# Capture any text between the end of the last tool call and the start of the current one
|
69 |
+
if start > last_end:
|
70 |
+
text_between = output_str[last_end:start].strip()
|
71 |
+
if text_between:
|
72 |
+
segments.append({"text": text_between, "type": "text"})
|
73 |
+
|
74 |
+
# Append the current tool call to the list
|
75 |
+
tool_call_content = match.group(2).strip()
|
76 |
+
segments.append({"tool_call": tool_call_content, "type": "function"})
|
77 |
+
|
78 |
+
# Update the last processed index
|
79 |
+
last_end = end
|
80 |
+
|
81 |
+
# Check if there is any remaining text after the last tool call
|
82 |
+
if last_end < len(output_str):
|
83 |
+
remaining_text = output_str[last_end:].strip()
|
84 |
+
if remaining_text:
|
85 |
+
segments.append({"text": remaining_text, "type": "text"})
|
86 |
+
|
87 |
+
return segments
|
88 |
+
|
89 |
+
def postprocess_output(output_str: str):
|
90 |
+
segments = extract_tool_calls_and_text(output_str)
|
91 |
+
results = []
|
92 |
+
|
93 |
+
for segment in segments:
|
94 |
+
print("processing segment")
|
95 |
+
print(segment)
|
96 |
+
if segment['type'] == 'function':
|
97 |
+
call = segment['tool_call']
|
98 |
+
try:
|
99 |
+
parsed_call = parse_json_safely(call)
|
100 |
+
cleaned_call = clean_json_object(parsed_call)
|
101 |
+
|
102 |
+
if isinstance(cleaned_call, dict) and 'name' in cleaned_call and 'arguments' in cleaned_call:
|
103 |
+
if isinstance(cleaned_call.get('arguments'), dict):
|
104 |
+
cleaned_call['arguments'] = json.dumps(cleaned_call['arguments'])
|
105 |
+
results.append({
|
106 |
+
"id": uuid.uuid4().hex[:8],
|
107 |
+
"function": cleaned_call,
|
108 |
+
"type": "function",
|
109 |
+
})
|
110 |
+
else:
|
111 |
+
results.append({
|
112 |
+
"id": uuid.uuid4().hex[:8],
|
113 |
+
"text": call,
|
114 |
+
"type": "text",
|
115 |
+
})
|
116 |
+
except Exception as e:
|
117 |
+
results.append({
|
118 |
+
"id": uuid.uuid4().hex[:8],
|
119 |
+
"text": call,
|
120 |
+
"type": "text",
|
121 |
+
})
|
122 |
+
else:
|
123 |
+
results.append({
|
124 |
+
"id": uuid.uuid4().hex[:8],
|
125 |
+
"text": segment['text'],
|
126 |
+
"type": "text",
|
127 |
+
})
|
128 |
+
|
129 |
+
return results
|
130 |
+
|
131 |
+
def json_to_markdown(json_obj):
|
132 |
+
"""Convert a JSON object to a formatted markdown string."""
|
133 |
+
markdown = ""
|
134 |
+
for item in json_obj:
|
135 |
+
if item.get("type") == "text":
|
136 |
+
# For text items, just add the text content
|
137 |
+
markdown += item.get("text", "") + "\n\n"
|
138 |
+
elif item.get("type") == "function":
|
139 |
+
# For function calls, format as JSON
|
140 |
+
markdown += "```json\n"
|
141 |
+
markdown += json.dumps(item.get("function", {}), indent=2)
|
142 |
+
markdown += "\n```\n\n"
|
143 |
+
return markdown.strip()
|
144 |
+
|
145 |
+
if __name__ == "__main__":
|
146 |
+
# Test the function with a sample input
|
147 |
+
# output_str = '''Some text before starttoolcall{"name": "funcA", "arguments": {"param1": 1}endtoolcall
|
148 |
+
# More text starttoolcall{"name": "funcB", "arguments": {"param2": "test"}}endtoolcall'''
|
149 |
+
|
150 |
+
# output_str = '''starttoolcall{"name": "get_current_weather", "arguments": {"location": "San Francisco", "unit": "celsius"}}endtoolcall starttoolcall{"name": "get_current_weather", "arguments": {"location": "Tokyo", "unit": "celsius"}}endtoolcall okay great '''
|
151 |
+
output_str = '''starttoolcall{"name": "get_current_weather", "arguments": {"location": "San Francisco", "unit": "celsius"}}endtoolcall starttoolcall{"name": "get_current_weather", "arguments": {"location": "Tokyo", "unit": "celsius"}}endtoolcall starttoolcall{"name": "get_current_weather", "arguments": {"location": "Paris", "unit": '''
|
152 |
+
parsed_json = postprocess_output(output_str)
|
153 |
+
print(json.dumps(parsed_json, indent=2))
|
154 |
+
|
155 |
+
print("-----")
|
156 |
+
print(json_to_markdown(parsed_json))
|
preprocess.py
ADDED
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import List
|
2 |
+
import json
|
3 |
+
|
4 |
+
|
5 |
+
TOOL_SYSTEM_PROMPT_RUBRA = (
|
6 |
+
"You have access to the following tools: {tool_text}\n"
|
7 |
+
"You can choose to respond with one or more tool calls at once, or with a chat message back to the user. "
|
8 |
+
"Ensure you have all necessary details before making tool calls. If additional information is needed, "
|
9 |
+
"ask the user appropriately. Any tool call you make must correspond to the functions listed above.\n"
|
10 |
+
"If you decide to call a tool, format it like this: "
|
11 |
+
'starttoolcall{{"name": "<function_name>", "arguments": {{"<arg1_name>": "<arg1_value>", "<arg2_name>": "<arg2_value>", ...}}}}endtoolcall '
|
12 |
+
"where the JSON wrapped between starttoolcall and endtoolcall represents the function call.\n"
|
13 |
+
)
|
14 |
+
|
15 |
+
def json_schema_to_typescript_type(schema, param_name):
|
16 |
+
ts_type = "any" # default type
|
17 |
+
enum_comment = ""
|
18 |
+
integer_comment = ""
|
19 |
+
description_comment = ""
|
20 |
+
|
21 |
+
if isinstance(schema, dict) and "type" in schema:
|
22 |
+
json_type = schema["type"]
|
23 |
+
if json_type == "array":
|
24 |
+
item_type = (
|
25 |
+
"any"
|
26 |
+
if "items" not in schema
|
27 |
+
else json_schema_to_typescript_type(schema["items"], param_name)[0]
|
28 |
+
)
|
29 |
+
ts_type = f"{item_type}[]"
|
30 |
+
elif json_type == "number":
|
31 |
+
ts_type = "number"
|
32 |
+
elif json_type == "integer":
|
33 |
+
ts_type = (
|
34 |
+
"number" # TypeScript doesn't differentiate between number and integer
|
35 |
+
)
|
36 |
+
integer_comment = f" * @param {param_name} - Integer"
|
37 |
+
elif json_type == "object":
|
38 |
+
ts_type, _ = generate_typescript_interface(schema, param_name)
|
39 |
+
elif json_type == "boolean":
|
40 |
+
ts_type = "boolean"
|
41 |
+
elif json_type == "null":
|
42 |
+
ts_type = "null"
|
43 |
+
elif json_type == "string":
|
44 |
+
ts_type = "string"
|
45 |
+
|
46 |
+
if "enum" in schema:
|
47 |
+
enum_comment = f" * @enum {param_name} - Possible values: " + ", ".join(
|
48 |
+
[f'"{enum_value}"' for enum_value in schema["enum"]]
|
49 |
+
)
|
50 |
+
ts_type = "string"
|
51 |
+
if "description" in schema:
|
52 |
+
description_comment = f' * @param {param_name} - {schema["description"]}'
|
53 |
+
|
54 |
+
# Return only the type for nested objects to avoid duplicating comments
|
55 |
+
if isinstance(schema, dict) and schema.get("type") == "object":
|
56 |
+
return ts_type, "", "", ""
|
57 |
+
|
58 |
+
return ts_type, enum_comment, integer_comment, description_comment
|
59 |
+
|
60 |
+
|
61 |
+
def generate_typescript_interface(schema, interface_name):
|
62 |
+
properties = schema.get("properties", {})
|
63 |
+
required = schema.get("required", [])
|
64 |
+
|
65 |
+
interface_body = []
|
66 |
+
descriptions = []
|
67 |
+
for prop_name, prop_schema in properties.items():
|
68 |
+
prop_type, enum_comment, integer_comment, description_comment = (
|
69 |
+
json_schema_to_typescript_type(prop_schema, prop_name)
|
70 |
+
)
|
71 |
+
is_optional = prop_name not in required
|
72 |
+
interface_body.append(
|
73 |
+
f' {prop_name}{"?" if is_optional else ""}: {prop_type};'
|
74 |
+
)
|
75 |
+
if description_comment:
|
76 |
+
descriptions.append(description_comment)
|
77 |
+
if enum_comment:
|
78 |
+
descriptions.append(enum_comment)
|
79 |
+
if integer_comment:
|
80 |
+
descriptions.append(integer_comment)
|
81 |
+
|
82 |
+
comments = "\n".join(descriptions)
|
83 |
+
interface_definition = (
|
84 |
+
f"interface {interface_name} {{\n" + "\n".join(interface_body) + "\n}"
|
85 |
+
)
|
86 |
+
return interface_definition, comments
|
87 |
+
|
88 |
+
|
89 |
+
def convert_parameters_list_to_dict(parameters):
|
90 |
+
properties = {}
|
91 |
+
required = []
|
92 |
+
for param in parameters:
|
93 |
+
properties[param["name"]] = param
|
94 |
+
if "default" not in param:
|
95 |
+
required.append(param["name"])
|
96 |
+
return {"properties": properties, "required": required}
|
97 |
+
|
98 |
+
|
99 |
+
def generate_typescript_function(function_schema) -> str:
|
100 |
+
func_name = function_schema["name"]
|
101 |
+
description = function_schema.get("description", "")
|
102 |
+
|
103 |
+
# Check if parameters is a list and convert if necessary
|
104 |
+
parameters_info = function_schema.get("parameters", {})
|
105 |
+
if isinstance(parameters_info, list):
|
106 |
+
parameters_info = convert_parameters_list_to_dict(parameters_info)
|
107 |
+
if parameters_info is None:
|
108 |
+
parameters_info = {}
|
109 |
+
|
110 |
+
parameters_schema = parameters_info.get("properties", {})
|
111 |
+
required_params = parameters_info.get("required", [])
|
112 |
+
|
113 |
+
args_list = []
|
114 |
+
comments_list = []
|
115 |
+
interfaces = []
|
116 |
+
for param_name, param_schema in parameters_schema.items():
|
117 |
+
ts_type, enum_comment, integer_comment, description_comment = (
|
118 |
+
json_schema_to_typescript_type(param_schema, param_name)
|
119 |
+
)
|
120 |
+
if ts_type.startswith("interface"):
|
121 |
+
interface_definition, nested_comments = generate_typescript_interface(
|
122 |
+
param_schema, f"{func_name}_{param_name.capitalize()}Params"
|
123 |
+
)
|
124 |
+
interfaces.append(interface_definition)
|
125 |
+
comments_list.append(nested_comments)
|
126 |
+
ts_type = f"{func_name}_{param_name.capitalize()}Params"
|
127 |
+
else:
|
128 |
+
if description_comment:
|
129 |
+
comments_list.append(description_comment)
|
130 |
+
if enum_comment:
|
131 |
+
comments_list.append(enum_comment)
|
132 |
+
if integer_comment:
|
133 |
+
comments_list.append(integer_comment)
|
134 |
+
is_optional = param_name not in required_params
|
135 |
+
args_list.append(f'{param_name}{"?" if is_optional else ""}: {ts_type}')
|
136 |
+
|
137 |
+
args_str = ", ".join(args_list)
|
138 |
+
comments_str = "\n".join(comments_list)
|
139 |
+
interfaces_str = "\n\n".join(interfaces)
|
140 |
+
|
141 |
+
description_comment = f" * {description}\n" if description else ""
|
142 |
+
typescript_func_declaration = (
|
143 |
+
"/**\n"
|
144 |
+
+ description_comment
|
145 |
+
+ (comments_str + "\n" if comments_str else "")
|
146 |
+
+ " */\n"
|
147 |
+
+ (interfaces_str + "\n\n" if interfaces_str else "")
|
148 |
+
+ f"function {func_name}({args_str}): any {{}}"
|
149 |
+
)
|
150 |
+
|
151 |
+
return typescript_func_declaration
|
152 |
+
|
153 |
+
|
154 |
+
|
155 |
+
def format_tools(tools: List[dict]) -> str:
|
156 |
+
func_defs = []
|
157 |
+
for t in tools:
|
158 |
+
tool_schema = t["function"] if "function" in t else t
|
159 |
+
func_defs.append(generate_typescript_function(tool_schema))
|
160 |
+
|
161 |
+
typescript_functions_str = "\n\n".join(func_defs)
|
162 |
+
res = TOOL_SYSTEM_PROMPT_RUBRA.format(tool_text=typescript_functions_str)
|
163 |
+
return res
|
164 |
+
|
165 |
+
|
166 |
+
|
167 |
+
def preprocess_input(msgs: List[dict], tools: List[dict]):
|
168 |
+
tool_system_prompt = format_tools(tools)
|
169 |
+
processed_msgs = process_messages(msgs, tool_system_prompt)
|
170 |
+
return processed_msgs
|
171 |
+
|
172 |
+
|
173 |
+
def process_messages(messages: List[dict], function_str: str):
|
174 |
+
func_observation_map = {}
|
175 |
+
processed_msg = []
|
176 |
+
|
177 |
+
for i in range(len(messages)):
|
178 |
+
|
179 |
+
if messages[i]["role"] != "tool" and len(func_observation_map) > 0:
|
180 |
+
# func_observation_array = [f'{k}: {func_observation_map[k] if func_observation_map[k] != "" else "done"}' for k in func_observation_map]
|
181 |
+
func_observation_array = [f'{func_observation_map[k] if func_observation_map[k] != "" else "done"}' for k in func_observation_map]
|
182 |
+
observation_str = json.dumps(func_observation_array)
|
183 |
+
observation_call = {"role": "user", "content": "start observation " + observation_str + " end observation"}
|
184 |
+
processed_msg.append(observation_call)
|
185 |
+
func_observation_map.clear()
|
186 |
+
|
187 |
+
if i == 0:
|
188 |
+
if messages[0]["role"] == "system":
|
189 |
+
old_content = messages[0]["content"]
|
190 |
+
sys_msg = {"role": "system", "content": old_content + "\n" + function_str}
|
191 |
+
processed_msg.append(sys_msg)
|
192 |
+
else:
|
193 |
+
# Insert a system message of tool definition before the first message
|
194 |
+
sys_msg = {"role": "system", "content": "You are a helpful assistant.\n" + function_str}
|
195 |
+
processed_msg.append(sys_msg)
|
196 |
+
processed_msg.append(messages[0]) # first message is always either system or user msg
|
197 |
+
|
198 |
+
elif messages[i]["role"] == "assistant" and "tool_calls" in messages[i]:
|
199 |
+
# Convert OpenAI function call format to Rubra format
|
200 |
+
tool_call_str = construct_tool_call_str(messages[i]["tool_calls"], func_observation_map)
|
201 |
+
function_call = {"role": "assistant", "content": tool_call_str}
|
202 |
+
processed_msg.append(function_call)
|
203 |
+
|
204 |
+
elif messages[i]["role"] == "tool":
|
205 |
+
tool_call_id = messages[i]["tool_call_id"]
|
206 |
+
if tool_call_id in func_observation_map:
|
207 |
+
func_observation_map[tool_call_id] = messages[i]["content"]
|
208 |
+
else:
|
209 |
+
print(func_observation_map)
|
210 |
+
print(f"Tool call id not found in the map: {tool_call_id}")
|
211 |
+
# TODO: the input is not valid in this case, should return an error
|
212 |
+
|
213 |
+
else:
|
214 |
+
processed_msg.append(messages[i])
|
215 |
+
|
216 |
+
|
217 |
+
if len(func_observation_map) > 0:
|
218 |
+
# func_observation_array = [f'{k}: {func_observation_map[k] if func_observation_map[k] != "" else "done"}' for k in func_observation_map]
|
219 |
+
func_observation_array = [f'{func_observation_map[k] if func_observation_map[k] != "" else "done"}' for k in func_observation_map]
|
220 |
+
observation_str = json.dumps(func_observation_array)
|
221 |
+
observation_call = {"role": "user", "content": "start observation " + observation_str + " end observation"}
|
222 |
+
processed_msg.append(observation_call)
|
223 |
+
func_observation_map.clear()
|
224 |
+
|
225 |
+
return processed_msg
|
226 |
+
|
227 |
+
|
228 |
+
def construct_tool_call_str(tool_calls, func_observation_map) -> str:
|
229 |
+
tool_list = []
|
230 |
+
for tool_call in tool_calls:
|
231 |
+
tool_call_id = tool_call["id"]
|
232 |
+
func_observation_map[tool_call_id] = "" # Initialize with empty value, updated later from the message with tool role
|
233 |
+
|
234 |
+
if type(tool_call["function"]["arguments"]) == str:
|
235 |
+
tool_call["function"]["arguments"] = json.loads(tool_call["function"]["arguments"])
|
236 |
+
tool_list.append("starttoolcall"+str(tool_call["function"]) + "endtoolcall")
|
237 |
+
|
238 |
+
# Converting the Python dictionary to a YAML formatted string
|
239 |
+
tool_call_str = "".join(tool_list)
|
240 |
+
return tool_call_str
|
241 |
+
|
242 |
+
|
243 |
+
if __name__ == "__main__":
|
244 |
+
tools = [{
|
245 |
+
"type": "function",
|
246 |
+
"function": {
|
247 |
+
"name": "dummy",
|
248 |
+
"description": "just to say hi",
|
249 |
+
"parameters": None,
|
250 |
+
}
|
251 |
+
},{"type": "function","function":{"name":"calculate_distance","description":"Calculate the distance between two locations","parameters":{"type":"object","properties":{"origin":{"type":"string","description":"The starting location"},"destination":{"type":"string","description":"The destination location"},"mode":{"type":"string","description":"The mode of transportation"}},"required":["origin","destination","mode"]}}},{"type": "function","function":{"name":"generate_password","description":"Generate a random password","parameters":{"type":"object","properties":{"length":{"type":"integer","description":"The length of the password"}},"required":["length"]}}}]
|
252 |
+
# msgs = [{'role': 'system', 'content': 'You are a helpful assistant.'}, {'role': 'user', 'content': 'What is the distance between San Francisco and Cupertino by driving and by air from both directions?'}, {'role': 'assistant', 'tool_calls': [{'id': '0', 'function': {'name': 'calculate_distance', 'arguments': '{"origin":"San Francisco","destination":"Cupertino","mode":"drive"}'}, 'type': 'function'},{'id': '1', 'function': {'name': 'calculate_distance', 'arguments': '{"origin":"San Francisco","destination":"Cupertino","mode":"air"}'}, 'type': 'function'}]}, {'role': 'tool', 'tool_call_id': '0', 'name': 'calculate_distance', 'content': 'Distance is 50 miles.'}, {'role': 'tool', 'tool_call_id': '1', 'name': 'calculate_distance', 'content': ''}]
|
253 |
+
msgs = [{'role': 'user', 'content': "\nYou are task oriented system.\nYou receive input from a user, process the input from the given instructions, and then output the result.\nYour objective is to provide consistent and correct results.\nYou do not need to explain the steps taken, only provide the result to the given instructions.\nYou are referred to as a tool.\nYou don't move to the next step until you have a result.\n\nDownload https://www.sqlitetutorial.net/wp-content/uploads/2018/03/chinook.zip to a\nrandom file. Then expand the archive to a temporary location as there is a sqlite\ndatabase in it.\n\nFirst inspect the schema of the database to understand the table structure.\n\nForm and run a SQL query to find the artist with the most number of albums and output\nthe result of that.\n\nWhen done remove the database file and the downloaded content."}, {'role': 'assistant', 'content': '', 'tool_calls': [{'id': 'cdf7eb2e', 'type': 'function', 'function': {'name': 'download', 'arguments': '{"url":"https://www.sqlitetutorial.net/wp-content/uploads/2018/03/chinook.zip"}'}}]}, {'role': 'tool', 'content': '/tmp/gpt-download2815959664.zip', 'name': 'download', 'tool_call_id': 'cdf7eb2e'}, {'role': 'assistant', 'content': '', 'tool_calls': [{'id': '07cdee86', 'type': 'function', 'function': {'name': 'exec', 'arguments': '{"command":"unzip /tmp/gpt-download2815959664.zip","directory":"."}'}}]}, {'role': 'tool', 'content': 'Archive: /tmp/gpt-download2815959664.zip\n inflating: chinook.db \n', 'name': 'exec', 'tool_call_id': '07cdee86'}, {'role': 'assistant', 'content': '', 'tool_calls': [{'id': 'e8042027', 'type': 'function', 'function': {'name': 'download', 'arguments': '{"url":"https://www.sqlitetutorial.net/wp-content/uploads/2018/03/chinook.zip"}'}}]}, {'role': 'tool', 'content': '/tmp/gpt-download3736408077.zip', 'name': 'download', 'tool_call_id': 'e8042027'}]
|
254 |
+
new_msgs = preprocess_input(msgs, tools)
|
255 |
+
print(json.dumps(new_msgs, indent=2))
|
256 |
+
|
requirements.txt
CHANGED
@@ -1 +1,11 @@
|
|
1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!npm install -g npm@latest
|
2 |
+
#!npm install -g jsonrepair
|
3 |
+
accelerate==0.30.1
|
4 |
+
bitsandbytes==0.43.1
|
5 |
+
gradio==4.36.1
|
6 |
+
scipy==1.13.0
|
7 |
+
sentencepiece==0.2.0
|
8 |
+
spaces==0.28.3
|
9 |
+
torch==2.0.0
|
10 |
+
transformers==4.41.0
|
11 |
+
pythonmonkey
|
style.css
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* Main container styling */
|
2 |
+
.gradio-container {
|
3 |
+
height: auto;
|
4 |
+
overflow: hidden; /* Prevents overflow outside the main container */
|
5 |
+
}
|
6 |
+
|
7 |
+
/* Main row configuration to fit within the screen */
|
8 |
+
#main-row {
|
9 |
+
display: flex; /* Ensures that columns within this row are aligned horizontally */
|
10 |
+
overflow: hidden; /* Prevents overflow outside the main row */
|
11 |
+
}
|
12 |
+
|
13 |
+
/* Configuration for the column containing the chatbot to ensure it doesn't grow too long */
|
14 |
+
.gr-column {
|
15 |
+
height: 100%; /* Sets column height to fill the parent container */
|
16 |
+
display: flex;
|
17 |
+
flex-direction: column;
|
18 |
+
overflow: hidden; /* Ensures no overflow outside the column */
|
19 |
+
}
|
20 |
+
|
21 |
+
/* Chatbot specific styling */
|
22 |
+
#chatbot {
|
23 |
+
flex-grow: 1; /* Allows chatbot to expand within the column space */
|
24 |
+
overflow-y: auto; /* Allows vertical scrolling within the chatbot */
|
25 |
+
}
|
26 |
+
|
27 |
+
/* General improvements for component layout and aesthetics */
|
28 |
+
.gr-row > .wrap > div, .gr-column > .wrap {
|
29 |
+
margin-bottom: 10px;
|
30 |
+
}
|
31 |
+
|
32 |
+
.gr-column {
|
33 |
+
gap: 10px;
|
34 |
+
align-items: flex-start; /* Aligns items to the start, preventing stretch */
|
35 |
+
}
|