Terry Zhuo commited on
Commit
e19a951
·
1 Parent(s): fb4be3f
Files changed (3) hide show
  1. app.py +45 -22
  2. requirements.txt +1 -1
  3. utils.py +148 -77
app.py CHANGED
@@ -5,6 +5,8 @@ from e2b_code_interpreter import Sandbox
5
  from pathlib import Path
6
  from peft import PeftModel
7
  from transformers import AutoTokenizer,AutoModelForCausalLM
 
 
8
  import json
9
 
10
  if not get_space():
@@ -20,6 +22,7 @@ from utils import (
20
  run_interactive_notebook,
21
  create_base_notebook,
22
  update_notebook_display,
 
23
  )
24
 
25
  E2B_API_KEY = os.environ["E2B_API_KEY"]
@@ -48,17 +51,12 @@ def execute_jupyter_agent(
48
  os.makedirs(save_dir, exist_ok=True)
49
  save_dir = os.path.join(save_dir, 'jupyter-agent.ipynb')
50
 
51
- tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-Coder-7B-Instruct")
52
- model = AutoModelForCausalLM.from_pretrained(
53
- "Qwen/Qwen2.5-Coder-7B-Instruct", torch_dtype='auto'
54
- ).eval()
55
- # # Load the LoRA adapter and move the model to GPU
56
- model = PeftModel.from_pretrained(
57
- model,
58
- model_name,
59
- device_map="auto", # Automatically allocate model layers to available devices
60
- trust_remote_code=True
61
- ).eval()
62
 
63
  filenames = []
64
  if files is not None:
@@ -69,7 +67,7 @@ def execute_jupyter_agent(
69
  sbx.files.write(filpath.name, file)
70
  filenames.append(filpath.name)
71
 
72
- # Initialize message_history if it doesn't exist
73
  if len(message_history) == 0:
74
  message_history.append(
75
  {
@@ -77,24 +75,49 @@ def execute_jupyter_agent(
77
  "content": system_prompt.format("- " + "\n- ".join(filenames)),
78
  }
79
  )
80
- message_history.append({"role": "user", "content": user_input})
 
 
 
 
 
 
 
 
 
 
81
 
82
  print("history:", message_history)
83
 
 
 
 
 
 
 
 
 
 
 
 
84
  for notebook_html, notebook_data, messages in run_interactive_notebook(
85
- model, tokenizer, message_history, sbx, max_new_tokens=max_new_tokens
86
  ):
87
  message_history = messages
88
 
89
- yield notebook_html, message_history, TMP_DIR+"jupyter-agent.ipynb"
90
-
91
- with open(save_dir, 'w', encoding='utf-8') as f:
92
- json.dump(notebook_data, f, indent=2)
93
- yield notebook_html, message_history, save_dir
94
 
95
  def clear(msg_state):
96
  msg_state = []
97
- return update_notebook_display(create_base_notebook([])[0]), msg_state
 
 
 
 
98
 
99
 
100
  css = """
@@ -151,9 +174,9 @@ with gr.Blocks() as demo:
151
  )
152
 
153
  model = gr.Dropdown(
154
- value="bigcomputer/jupycoder-7b-lora-350",
155
  choices=[
156
- "bigcomputer/jupycoder-7b-lora-350",
157
  "Qwen/Qwen2.5-Coder-7B-Instruct"
158
  ],
159
  label="Models"
 
5
  from pathlib import Path
6
  from peft import PeftModel
7
  from transformers import AutoTokenizer,AutoModelForCausalLM
8
+ from huggingface_hub import snapshot_download
9
+ from vllm import LLM, SamplingParams
10
  import json
11
 
12
  if not get_space():
 
22
  run_interactive_notebook,
23
  create_base_notebook,
24
  update_notebook_display,
25
+ user_template,
26
  )
27
 
28
  E2B_API_KEY = os.environ["E2B_API_KEY"]
 
51
  os.makedirs(save_dir, exist_ok=True)
52
  save_dir = os.path.join(save_dir, 'jupyter-agent.ipynb')
53
 
54
+ sampling_params = SamplingParams(
55
+ temperature=0.2,
56
+ max_tokens=512,
57
+ )
58
+
59
+ lora_path = snapshot_download(model_name)
 
 
 
 
 
60
 
61
  filenames = []
62
  if files is not None:
 
67
  sbx.files.write(filpath.name, file)
68
  filenames.append(filpath.name)
69
 
70
+ # Initialize message_history and notebook_data if they don't exist
71
  if len(message_history) == 0:
72
  message_history.append(
73
  {
 
75
  "content": system_prompt.format("- " + "\n- ".join(filenames)),
76
  }
77
  )
78
+ current_notebook = None
79
+ else:
80
+ # Load existing notebook data from file
81
+ try:
82
+ with open(save_dir, 'r', encoding='utf-8') as f:
83
+ current_notebook = json.load(f)
84
+ except (FileNotFoundError, json.JSONDecodeError):
85
+ current_notebook = None
86
+
87
+ # Add user input with is_user_prompt flag
88
+ message_history.append({"role": "user", "content": user_input, "is_user_prompt": True})
89
 
90
  print("history:", message_history)
91
 
92
+ # Update notebook with new user prompt if we have an existing notebook
93
+ if current_notebook is not None:
94
+ current_notebook["cells"].append({
95
+ "cell_type": "markdown",
96
+ "metadata": {},
97
+ "source": user_template.format(user_input.replace('\n', '<br>'))
98
+ })
99
+ # Save the updated notebook
100
+ with open(save_dir, 'w', encoding='utf-8') as f:
101
+ json.dump(current_notebook, f, indent=2)
102
+
103
  for notebook_html, notebook_data, messages in run_interactive_notebook(
104
+ lora_path, sampling_params, message_history, sbx, notebook_data=current_notebook, max_new_tokens=max_new_tokens
105
  ):
106
  message_history = messages
107
 
108
+ # Save notebook after each update
109
+ with open(save_dir, 'w', encoding='utf-8') as f:
110
+ json.dump(notebook_data, f, indent=2)
111
+
112
+ yield notebook_html, message_history, save_dir
113
 
114
  def clear(msg_state):
115
  msg_state = []
116
+ # Also clear the notebook file
117
+ notebook_data = create_base_notebook([])[0]
118
+ with open(TMP_DIR+"jupyter-agent.ipynb", 'w', encoding='utf-8') as f:
119
+ json.dump(notebook_data, f, indent=2)
120
+ return update_notebook_display(notebook_data), msg_state
121
 
122
 
123
  css = """
 
174
  )
175
 
176
  model = gr.Dropdown(
177
+ value="bigcomputer/jupycoder-7b-lora-200",
178
  choices=[
179
+ "bigcomputer/jupycoder-7b-lora-200",
180
  "Qwen/Qwen2.5-Coder-7B-Instruct"
181
  ],
182
  label="Models"
requirements.txt CHANGED
@@ -4,4 +4,4 @@ huggingface_hub
4
  e2b-code-interpreter
5
  transformers
6
  traitlets
7
- peft
 
4
  e2b-code-interpreter
5
  transformers
6
  traitlets
7
+ vllm
utils.py CHANGED
@@ -3,12 +3,14 @@ from nbformat.v4 import new_notebook, new_markdown_cell, new_code_cell
3
  from nbconvert import HTMLExporter
4
  from huggingface_hub import InferenceClient
5
  from e2b_code_interpreter import Sandbox
6
- from transformers import AutoTokenizer
7
  from traitlets.config import Config
 
8
  import re
9
 
10
  config = Config()
11
  html_exporter = HTMLExporter(config=config, template_name="classic")
 
12
 
13
  # Constants
14
  MAX_TURNS = 10
@@ -157,11 +159,20 @@ def create_base_notebook(messages):
157
  "source": text
158
  })
159
  elif message["role"] == "user":
160
- text = user_template.format(message["content"].replace('\n', '<br>'))
161
- base_notebook["cells"].append({
162
- "cell_type": "markdown",
163
- "metadata": {},
164
- "source": text
 
 
 
 
 
 
 
 
 
165
  })
166
 
167
  elif message["role"] == "assistant" and "tool_calls" in message:
@@ -219,84 +230,144 @@ def update_notebook_display(notebook_data):
219
  notebook_body = notebook_body.replace(bad_html_bad, "")
220
  return notebook_body
221
 
222
- def run_interactive_notebook(model, tokenizer, messages, sbx, max_new_tokens=512):
223
- notebook_data, code_cell_counter = create_base_notebook(messages)
224
- turns = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
- while turns <= MAX_TURNS:
 
227
  turns += 1
228
- # Generate response using the model
229
- text = tokenizer.apply_chat_template(
230
- messages, tokenize=False, add_generation_prompt=True
231
- )
232
- model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
233
-
234
- generated_ids = model.generate(
235
- **model_inputs,
236
- max_new_tokens=max_new_tokens
 
 
 
 
237
  )
238
- generated_ids = [
239
- output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
240
- ]
241
- response_stream = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
242
 
243
- # Process the full response at once
244
- parts = re.split(r'(```python[\s\S]*?```)', response_stream)
 
 
245
 
246
- for part in parts:
247
- if part.strip():
248
- if part.startswith('```python'):
249
- # Extract code without the markers
250
- code = re.sub(r'```python\n|```', '', part).strip()
251
- code_cell_counter += 1
252
-
253
- # Add code cell
254
- notebook_data["cells"].append({
255
- "cell_type": "code",
256
- "execution_count": code_cell_counter,
257
- "metadata": {},
258
- "source": code,
259
- "outputs": []
260
- })
261
-
262
- # Execute code
263
- exec_result, execution = execute_code(sbx, code)
264
- messages.append({
265
- "role": "assistant",
266
- "content": code,
267
- "tool_calls": [{
268
- "type": "function",
269
- "function": {
270
- "name": "code_interpreter",
271
- "arguments": {"code": code}
272
- }
273
- }]
274
- })
275
- messages.append({
276
- "role": "ipython",
277
- "content": parse_exec_result_llm(execution),
278
- "nbformat": parse_exec_result_nb(execution)
279
- })
280
-
281
- # Update cell with execution results
282
- notebook_data["cells"][-1]["outputs"] = parse_exec_result_nb(execution)
283
- else:
284
- # Add markdown cell for non-code content
285
- notebook_data["cells"].append({
286
- "cell_type": "markdown",
287
- "metadata": {},
288
- "source": part.strip()
289
- })
290
- messages.append({
291
- "role": "assistant",
292
- "content": part.strip()
293
- })
294
 
295
- # Return the final result
296
- yield update_notebook_display(notebook_data), notebook_data, messages
297
- break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
 
299
- yield update_notebook_display(notebook_data), notebook_data, messages
 
300
 
301
  def update_notebook_with_cell(notebook_data, code, output):
302
  """Add a code cell and its output to the notebook"""
 
3
  from nbconvert import HTMLExporter
4
  from huggingface_hub import InferenceClient
5
  from e2b_code_interpreter import Sandbox
6
+ from vllm.lora.request import LoRARequest
7
  from traitlets.config import Config
8
+ from vllm import LLM
9
  import re
10
 
11
  config = Config()
12
  html_exporter = HTMLExporter(config=config, template_name="classic")
13
+ BASE_MODEL = LLM(model="Qwen/Qwen2.5-Coder-7B-Instruct", enable_lora=True)
14
 
15
  # Constants
16
  MAX_TURNS = 10
 
159
  "source": text
160
  })
161
  elif message["role"] == "user":
162
+ # Check if this is an actual user prompt (has is_user_prompt flag)
163
+ if message.get("is_user_prompt", False):
164
+ text = user_template.format(message["content"].replace('\n', '<br>'))
165
+ base_notebook["cells"].append({
166
+ "cell_type": "markdown",
167
+ "metadata": {},
168
+ "source": text
169
+ })
170
+ else:
171
+ # This is an execution output, add as code cell output
172
+ base_notebook["cells"][-1]["outputs"].append({
173
+ "output_type": "stream",
174
+ "name": "stdout",
175
+ "text": message["content"]
176
  })
177
 
178
  elif message["role"] == "assistant" and "tool_calls" in message:
 
230
  notebook_body = notebook_body.replace(bad_html_bad, "")
231
  return notebook_body
232
 
233
+ def run_interactive_notebook(lora_path, sampling_params, messages, sbx, notebook_data=None, max_new_tokens=512):
234
+ """
235
+ Run interactive notebook with model.
236
+
237
+ Args:
238
+ lora_path: Path to LoRA adapter
239
+ sampling_params: Sampling parameters for the model
240
+ messages: List of conversation messages
241
+ sbx: Sandbox environment for code execution
242
+ notebook_data: Existing notebook data when continuing a session
243
+ max_new_tokens: Maximum number of new tokens to generate
244
+ """
245
+ # For first run or when notebook_data is not provided
246
+ if notebook_data is None:
247
+ # Create a separate list for display messages with is_user_prompt flag
248
+ display_messages = []
249
+ model_messages = [] # Clean messages for model
250
+ for msg in messages:
251
+ display_msg = msg.copy()
252
+ if msg["role"] == "user":
253
+ display_msg["is_user_prompt"] = True
254
+ display_messages.append(display_msg)
255
+ model_messages.append(msg.copy()) # Keep clean copy for model
256
+ notebook_data, code_cell_counter = create_base_notebook(display_messages)
257
+ else:
258
+ # For subsequent runs, use existing messages but clean them for model
259
+ display_messages = messages
260
+ model_messages = []
261
+ for msg in messages:
262
+ # Create clean copy without display flags for model
263
+ model_msg = msg.copy()
264
+ if "is_user_prompt" in model_msg:
265
+ del model_msg["is_user_prompt"]
266
+ model_messages.append(model_msg)
267
+
268
+ # Find the last code cell counter
269
+ code_cell_counter = 0
270
+ for cell in notebook_data["cells"]:
271
+ if cell["cell_type"] == "code" and cell.get("execution_count"):
272
+ code_cell_counter = max(code_cell_counter, cell["execution_count"])
273
 
274
+ turns = 0
275
+ while turns < MAX_TURNS:
276
  turns += 1
277
+ # Generate response using the model with clean messages
278
+ print(model_messages)
279
+ response_stream = BASE_MODEL.chat(
280
+ model_messages,
281
+ sampling_params,
282
+ lora_request=LoRARequest("lora_adapter", 1, lora_path),
283
+ add_generation_prompt=True
284
+ )[0].outputs[0].text
285
+
286
+ # Check for duplicate responses
287
+ is_duplicate = any(
288
+ msg["role"] == "assistant" and msg["content"].strip() == response_stream.strip()
289
+ for msg in model_messages
290
  )
 
 
 
 
291
 
292
+ if is_duplicate:
293
+ # If duplicate found, yield current state and break
294
+ yield update_notebook_display(notebook_data), notebook_data, display_messages
295
+ break
296
 
297
+ # Add the full response as an assistant message
298
+ assistant_msg = {
299
+ "role": "assistant",
300
+ "content": response_stream
301
+ }
302
+ model_messages.append(assistant_msg.copy())
303
+ display_messages.append(assistant_msg)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
304
 
305
+ # Check if response contains code block
306
+ code_match = re.search(r'```python\n(.*?)```', response_stream, re.DOTALL)
307
+ if code_match:
308
+ # Extract and execute the code
309
+ code = code_match.group(1).strip()
310
+ code_cell_counter += 1
311
+
312
+ # Add code cell
313
+ notebook_data["cells"].append({
314
+ "cell_type": "code",
315
+ "execution_count": code_cell_counter,
316
+ "metadata": {},
317
+ "source": code,
318
+ "outputs": []
319
+ })
320
+
321
+ # Execute code and get results
322
+ exec_result, execution = execute_code(sbx, code)
323
+
324
+ # Get execution results in notebook format
325
+ outputs = parse_exec_result_nb(execution)
326
+
327
+ # Create text-only version for user message
328
+ user_content = []
329
+ for output in outputs:
330
+ if output.get('output_type') == 'stream':
331
+ user_content.append(output['text'])
332
+ elif output.get('output_type') == 'error':
333
+ user_content.append('\n'.join(output['traceback']))
334
+ elif output.get('output_type') in ['execute_result', 'display_data']:
335
+ data = output.get('data', {})
336
+ if 'text/plain' in data:
337
+ user_content.append('\n'.join(data['text/plain']))
338
+ if any(key.startswith('image/') for key in data.keys()):
339
+ user_content.append('<image>')
340
+
341
+ # Create execution result message
342
+ user_msg = {
343
+ "role": "user",
344
+ "content": '\n'.join(user_content)
345
+ }
346
+ # Add clean version to model messages
347
+ model_messages.append(user_msg.copy())
348
+ # Add version with display flag to display messages
349
+ display_msg = user_msg.copy()
350
+ display_msg["is_user_prompt"] = False
351
+ display_messages.append(display_msg)
352
+
353
+ # Update cell with execution results
354
+ notebook_data["cells"][-1]["outputs"] = outputs
355
+
356
+ # Yield intermediate results after each turn
357
+ yield update_notebook_display(notebook_data), notebook_data, display_messages
358
+ else:
359
+ # No code in this turn, add as markdown and break
360
+ notebook_data["cells"].append({
361
+ "cell_type": "markdown",
362
+ "metadata": {},
363
+ "source": response_stream
364
+ })
365
+ # Yield final results and break
366
+ yield update_notebook_display(notebook_data), notebook_data, display_messages
367
+ break
368
 
369
+ # Final yield in case we hit MAX_TURNS
370
+ yield update_notebook_display(notebook_data), notebook_data, display_messages
371
 
372
  def update_notebook_with_cell(notebook_data, code, output):
373
  """Add a code cell and its output to the notebook"""