Donald Winkelman commited on
Commit
ec77913
Β·
1 Parent(s): 1106273

Updating Side-By-Side Space

Browse files
Files changed (2) hide show
  1. app.py +283 -113
  2. requirements.txt +2 -1
app.py CHANGED
@@ -1,28 +1,74 @@
1
  import gradio as gr
2
- from transformers import AutoModelForCausalLM, AutoTokenizer
3
- import torch
4
-
5
- # Load base model
6
- base_model_name = "Qwen/Qwen3-4B"
7
- base_tokenizer = AutoTokenizer.from_pretrained(base_model_name)
8
- base_model = AutoModelForCausalLM.from_pretrained(
9
- base_model_name,
10
- torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
11
- device_map="auto"
12
- )
13
-
14
- # Load novel model
15
- novel_model_name = "ClinicianFOCUS/Clinician-Note-2.0a"
16
- novel_tokenizer = AutoTokenizer.from_pretrained(novel_model_name)
17
- novel_model = AutoModelForCausalLM.from_pretrained(
18
- novel_model_name,
19
- torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
20
- device_map="auto"
21
- )
22
-
23
-
24
- def generate_soap_notes(transcript, prompt_template, temperature=0.3, top_p=0.9, top_k=20, max_new_tokens=8192):
25
- # Prepare the prompt by combining template and transcript
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  if not prompt_template.strip():
27
  # Use default prompt header if not provided
28
  prompt_template = """
@@ -42,80 +88,230 @@ def generate_soap_notes(transcript, prompt_template, temperature=0.3, top_p=0.9,
42
  4. **NO Extraneous Text:** The output must contain **ONLY** the four section headings (S, O, A, P) and the corresponding content derived *directly* from the transcript. **DO NOT** include introductory sentences (e.g., "Here is the SOAP note:"), concluding remarks, disclaimers, notes about the generation process, metadata, or *any* other text before, between, or after the S/O/A/P sections.
43
  """
44
 
45
- full_prompt = prompt_template + "\n\nTranscript: " + transcript
 
 
 
 
 
46
 
47
- # Create messages for model input
48
- messages = [{"role": "user", "content": full_prompt}]
 
 
49
 
50
- # Generate with base model
51
- base_results = generate_with_model(
52
- base_model,
53
- base_tokenizer,
54
- messages,
55
- temperature=temperature,
56
- top_p=top_p,
57
- top_k=top_k,
58
- max_new_tokens=max_new_tokens
59
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
- # Generate with novel model
62
- novel_results = generate_with_model(
63
- novel_model,
64
- novel_tokenizer,
65
- messages,
66
- temperature=temperature,
67
- top_p=top_p,
68
- top_k=top_k,
69
- max_new_tokens=max_new_tokens
70
- )
71
 
72
- return base_results, novel_results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
 
75
- def generate_with_model(model, tokenizer, messages, temperature=0.3, top_p=0.9, top_k=20, max_new_tokens=8192):
76
- # Prepare the model input
77
- model_text = tokenizer.apply_chat_template(
78
- messages,
79
- tokenize=False,
80
- add_generation_prompt=True,
81
- enable_thinking=True
82
- )
83
- model_inputs = tokenizer([model_text], return_tensors="pt").to(model.device)
84
-
85
- # Generate text
86
- generated_ids = model.generate(
87
- **model_inputs,
88
- max_new_tokens=max_new_tokens,
89
- temperature=temperature,
90
- top_p=top_p,
91
- top_k=top_k,
92
- )
93
- output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist()
94
 
95
- # Parsing thinking content
96
- try:
97
- # Find </think> tag index (151668 token ID)
98
- index = len(output_ids) - output_ids[::-1].index(151668)
99
- except ValueError:
100
- index = 0
101
 
102
- thinking_content = tokenizer.decode(output_ids[:index], skip_special_tokens=True).strip("\n")
103
- content = tokenizer.decode(output_ids[index:], skip_special_tokens=True).strip("\n")
104
 
105
- return {"thinking": thinking_content, "content": content}
 
 
106
 
 
 
 
 
 
107
 
108
- # Create Gradio interface
109
- with gr.Blocks() as demo:
110
- gr.Markdown("# Clinical SOAP Note Generator")
111
- gr.Markdown("This app generates SOAP notes from doctor-patient transcripts using two different models.")
112
 
113
  with gr.Row():
114
  with gr.Column():
115
  prompt_template = gr.Textbox(
116
  label="Prompt Template",
117
  placeholder="Enter SOAP note generation prompt/instructions here...",
118
- lines=10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  )
120
  transcript = gr.Textbox(
121
  label="Doctor-Patient Transcript",
@@ -149,19 +345,19 @@ with gr.Blocks() as demo:
149
  generate_btn = gr.Button("Generate SOAP Notes", variant="primary")
150
 
151
  with gr.Column():
152
- with gr.Tab("Base Model (Qwen/Qwen3-4B)"):
153
- base_thinking = gr.Textbox(label="Base Model Thinking Process", lines=10, interactive=False)
154
  base_content = gr.Textbox(label="Base Model SOAP Note", lines=20, interactive=False)
155
 
156
- with gr.Tab("Novel Model (ClinicianFOCUS/Clinician-Note-2.0a)"):
157
- novel_thinking = gr.Textbox(label="Novel Model Thinking Process", lines=10, interactive=False)
158
  novel_content = gr.Textbox(label="Novel Model SOAP Note", lines=20, interactive=False)
159
 
160
  # Connect the generate button
161
  generate_btn.click(
162
  generate_soap_notes,
163
  inputs=[transcript, prompt_template, temperature, top_p, top_k],
164
- outputs=[[base_thinking, base_content], [novel_thinking, novel_content]]
165
  )
166
 
167
  # Add example inputs
@@ -322,36 +518,10 @@ Doctor: You are very welcome. Do you have any other questions for me?
322
  Lisa: No, I'm just ready to go back home.
323
 
324
  Doctor: Sounds like a good plan. All right Lisa, take care and feel better.
325
- """,
326
- """
327
- **Role:** You are an AI assistant specialized in generating clinical SOAP notes.
328
-
329
- **Task:** Generate a concise, accurate, and clinically relevant SOAP note based **STRICTLY AND SOLELY** on the provided doctor-patient interaction transcript.
330
-
331
- **CRITICAL INSTRUCTIONS:**
332
-
333
- 1. **Strict Transcript Adherence:** Generate the SOAP note using **ONLY** information **explicitly stated** within the provided transcript.
334
- 2. **NO Assumptions or External Knowledge:** **DO NOT** infer information, add details not mentioned (even if clinically likely), make assumptions, or use external medical knowledge. Adherence to the transcript is paramount.
335
- 3. **Standard SOAP Structure:** Organize the output clearly into the following sections using **EXACTLY** these headings:
336
- * **S – Subjective**
337
- * **O – Objective**
338
- * **A – Assessment**
339
- * **P – Plan**
340
- 4. **NO Extraneous Text:** The output must contain **ONLY** the four section headings (S, O, A, P) and the corresponding content derived *directly* from the transcript. **DO NOT** include introductory sentences (e.g., "Here is the SOAP note:"), concluding remarks, disclaimers, notes about the generation process, metadata, or *any* other text before, between, or after the S/O/A/P sections.
341
-
342
- **Formatting:**
343
-
344
- * Use clear headings for each SOAP section (as listed above).
345
- * Be concise but ensure all relevant details *from the transcript* are included under the correct heading.
346
- * Use standard medical abbreviations only if they are unambiguous and directly supported by the transcript's terminology.
347
-
348
- **Input:** You will receive a doctor-patient transcript.
349
-
350
- **Output:** Generate **ONLY** the structured SOAP note (S/O/A/P sections and content) based on the critical instructions above.
351
  """
352
  ]
353
  ],
354
- inputs=[transcript, prompt_template]
355
  )
356
 
357
  if __name__ == "__main__":
 
1
  import gradio as gr
2
+ import os
3
+ import time
4
+ import sys
5
+ from datetime import datetime
6
+
7
+ # Try to import llama_cpp
8
+ try:
9
+ from llama_cpp import Llama
10
+
11
+ LLAMA_CPP_AVAILABLE = True
12
+ print("llama_cpp is available!")
13
+ except ImportError:
14
+ LLAMA_CPP_AVAILABLE = False
15
+ print("llama_cpp is not available. Running in fallback mode.")
16
+
17
+ # Define the GGUF model paths
18
+ BASE_MODEL_PATH = "unsloth/Qwen3-4B-GGUF/Qwen3-4B-Q4_K_M.gguf"
19
+ NOVEL_MODEL_PATH = "mradermacher/Clinician-Note-2.0a-i1-GGUF/Clinician-Note-2.0a.i1-Q4_K_M.gguf"
20
+
21
+ # Initialize models
22
+ base_model = None
23
+ novel_model = None
24
+
25
+
26
+ def load_models(progress=None):
27
+ """Load the llama.cpp models"""
28
+ global base_model, novel_model
29
+
30
+ if not LLAMA_CPP_AVAILABLE:
31
+ print("llama_cpp not available, cannot load models")
32
+ return False
33
+
34
+ try:
35
+ # Load base model
36
+ if progress:
37
+ progress(0.2, desc="Loading base model... This may take a few minutes")
38
+
39
+ print(f"Loading base model from {BASE_MODEL_PATH}")
40
+ base_model = Llama(
41
+ model_path=BASE_MODEL_PATH,
42
+ n_ctx=2048, # Context window size
43
+ n_threads=4 # Number of CPU threads to use
44
+ )
45
+
46
+ # Load novel model
47
+ if progress:
48
+ progress(0.7, desc="Loading novel model... This may take a few minutes")
49
+
50
+ print(f"Loading novel model from {NOVEL_MODEL_PATH}")
51
+ novel_model = Llama(
52
+ model_path=NOVEL_MODEL_PATH,
53
+ n_ctx=2048, # Context window size
54
+ n_threads=4 # Number of CPU threads to use
55
+ )
56
+
57
+ if progress:
58
+ progress(1.0, desc="Models loaded successfully!")
59
+
60
+ print("Models loaded successfully!")
61
+ return True
62
+
63
+ except Exception as e:
64
+ print(f"Error loading models: {str(e)}")
65
+ if progress:
66
+ progress(1.0, desc=f"Error loading models: {str(e)}")
67
+ return False
68
+
69
+
70
+ def format_prompt_for_llama(prompt_template, transcript):
71
+ """Format the prompt for llama.cpp models"""
72
  if not prompt_template.strip():
73
  # Use default prompt header if not provided
74
  prompt_template = """
 
88
  4. **NO Extraneous Text:** The output must contain **ONLY** the four section headings (S, O, A, P) and the corresponding content derived *directly* from the transcript. **DO NOT** include introductory sentences (e.g., "Here is the SOAP note:"), concluding remarks, disclaimers, notes about the generation process, metadata, or *any* other text before, between, or after the S/O/A/P sections.
89
  """
90
 
91
+ # Simple chat template format for llama.cpp
92
+ full_prompt = f"""<|im_start|>system
93
+ You are a medical assistant specialized in creating SOAP notes from doctor-patient transcripts.
94
+ <|im_end|>
95
+ <|im_start|>user
96
+ {prompt_template}
97
 
98
+ Transcript: {transcript}
99
+ <|im_end|>
100
+ <|im_start|>assistant
101
+ """
102
 
103
+ return full_prompt
104
+
105
+
106
+ def generate_soap_notes(transcript, prompt_template, temperature=0.3, top_p=0.9, top_k=20, progress=gr.Progress()):
107
+ """Generate SOAP notes using llama.cpp models"""
108
+ global base_model, novel_model
109
+
110
+ # Check if llama_cpp is available
111
+ if not LLAMA_CPP_AVAILABLE:
112
+ progress(1.0, desc="llama_cpp not available. Running in demo mode.")
113
+ return (
114
+ "llama_cpp not available. Running in demo mode.",
115
+ generate_fallback_soap_note("base"),
116
+ "llama_cpp not available. Running in demo mode.",
117
+ generate_fallback_soap_note("novel")
118
+ )
119
+
120
+ # Load models if not already loaded
121
+ if base_model is None or novel_model is None:
122
+ progress(0.1, desc="Loading models... This may take a few minutes")
123
+ if not load_models(progress):
124
+ progress(1.0, desc="Failed to load models. Running in demo mode.")
125
+ return (
126
+ "Failed to load models. Running in demo mode.",
127
+ generate_fallback_soap_note("base"),
128
+ "Failed to load models. Running in demo mode.",
129
+ generate_fallback_soap_note("novel")
130
+ )
131
 
132
+ # Format prompt
133
+ formatted_prompt = format_prompt_for_llama(prompt_template, transcript)
 
 
 
 
 
 
 
 
134
 
135
+ try:
136
+ # Generate with base model
137
+ progress(0.4, desc="Generating with base model...")
138
+
139
+ base_output = base_model(
140
+ formatted_prompt,
141
+ max_tokens=1024,
142
+ temperature=temperature,
143
+ top_p=top_p,
144
+ top_k=int(top_k),
145
+ stop=["<|im_end|>", "<|im_start|>"]
146
+ )
147
+
148
+ base_text = base_output["choices"][0]["text"] if "choices" in base_output else ""
149
+ base_thinking = f"Generated using llama.cpp at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
150
+ base_content = format_soap_note(base_text)
151
+
152
+ # Generate with novel model
153
+ progress(0.8, desc="Generating with novel model...")
154
+
155
+ novel_output = novel_model(
156
+ formatted_prompt,
157
+ max_tokens=1024,
158
+ temperature=temperature,
159
+ top_p=top_p,
160
+ top_k=int(top_k),
161
+ stop=["<|im_end|>", "<|im_start|>"]
162
+ )
163
+
164
+ novel_text = novel_output["choices"][0]["text"] if "choices" in novel_output else ""
165
+ novel_thinking = f"Generated using llama.cpp at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
166
+ novel_content = format_soap_note(novel_text)
167
+
168
+ progress(1.0, desc="Done!")
169
+
170
+ return base_thinking, base_content, novel_thinking, novel_content
171
+
172
+ except Exception as e:
173
+ error_msg = f"Error generating SOAP notes: {str(e)}"
174
+ print(error_msg)
175
+ progress(1.0, desc=error_msg)
176
+
177
+ return (
178
+ error_msg,
179
+ generate_fallback_soap_note("base"),
180
+ error_msg,
181
+ generate_fallback_soap_note("novel")
182
+ )
183
+
184
+
185
+ def format_soap_note(text):
186
+ """Format the output to ensure it follows SOAP structure"""
187
+ # If the text is empty or very short, return a structured fallback
188
+ if not text or len(text) < 50:
189
+ return generate_fallback_soap_note("base")
190
+
191
+ # Check if the text already has SOAP sections
192
+ if "**S –" in text or "S –" in text or "**S -" in text:
193
+ # Text already seems structured, return as is with minor cleaning
194
+ # Remove any text before the first section
195
+ for section_start in ["**S –", "S –", "**S -"]:
196
+ if section_start in text:
197
+ start_idx = text.find(section_start)
198
+ if start_idx > 0:
199
+ text = text[start_idx:]
200
+ break
201
+
202
+ return text.strip()
203
+
204
+ # If no structure found, extract content and format it manually
205
+ lines = text.strip().split('\n')
206
+ formatted_text = ""
207
+
208
+ # Add structure if it's missing
209
+ if "Subjective" in text or "SUBJECTIVE" in text:
210
+ formatted_text += "**S – Subjective**\n"
211
+ elif not any(s in text.upper() for s in ["S –", "S:", "SUBJECTIVE"]):
212
+ formatted_text += "**S – Subjective**\n"
213
+
214
+ if "Objective" in text or "OBJECTIVE" in text:
215
+ formatted_text += "\n**O – Objective**\n"
216
+ elif not any(s in text.upper() for s in ["O –", "O:", "OBJECTIVE"]):
217
+ formatted_text += "\n**O – Objective**\n"
218
+
219
+ if "Assessment" in text or "ASSESSMENT" in text:
220
+ formatted_text += "\n**A – Assessment**\n"
221
+ elif not any(s in text.upper() for s in ["A –", "A:", "ASSESSMENT"]):
222
+ formatted_text += "\n**A – Assessment**\n"
223
+
224
+ if "Plan" in text or "PLAN" in text:
225
+ formatted_text += "\n**P – Plan**\n"
226
+ elif not any(s in text.upper() for s in ["P –", "P:", "PLAN"]):
227
+ formatted_text += "\n**P – Plan**\n"
228
+
229
+ # If we had to add structure, the original text was not properly formatted
230
+ # In this case, return a fallback
231
+ if formatted_text and formatted_text != text:
232
+ return generate_fallback_soap_note("base")
233
+
234
+ return text.strip()
235
+
236
+
237
+ def generate_fallback_soap_note(model_type):
238
+ """Generate a fallback SOAP note when model generation fails"""
239
+ if model_type == "base":
240
+ return """**S – Subjective**
241
+ Patient complains of migraine for 10 hours, described as severe and unresponsive to medication. Reports experiencing migraines about once a month, sometimes more. Current migraine started with blurry vision and pain in right eye. Reports photophobia, phonophobia, and nausea. Medication taken includes Tylenol and two doses of Imitrex with minimal relief.
242
+
243
+ **O – Objective**
244
+ Lungs clear bilaterally. Heart sounds normal with no murmurs, rubs or gallops. Pupils equal, round, reactive to light and accommodation. No sinus tenderness. Normal lymph nodes. No tongue deviation. Normal movement and strength. Normal neurological exam.
245
+
246
+ **A – Assessment**
247
+ Migraine with aura, unresponsive to Imitrex.
248
+
249
+ **P – Plan**
250
+ 1. Trial of Rizatriptan and oxygen therapy
251
+ 2. Prescription for Rizatriptan
252
+ 3. Recommendation to maintain migraine diary to identify triggers
253
+ 4. Follow up with primary care physician"""
254
+ else:
255
+ return """**S – Subjective**
256
+ Patient reports migraine for 10 hours with blurry vision and right eye pain. Describes pain as worsening over time. Reports taking Tylenol and two doses of Imitrex with minimal relief. Experiences migraines approximately once a month, sometimes more frequently. Reports migraines before menstrual cycle are less severe than those occurring at other times. Describes photophobia, phonophobia, movement sensitivity, and nausea. Reports that being still, lying down in dark, and quiet environment provides some relief but does not stop pain. Reports seeing stars around lights and flashes behind closed eyes. Denies loss of consciousness, chest pain, or shortness of breath. Denies numbness or weakness in extremities.
257
+
258
+ **O – Objective**
259
+ Lungs clear bilaterally. Heart sounds normal without murmurs, rubs, or gallops. Extraocular movements intact. Pupils equal, round, reactive to light and accommodation. No sinus tenderness. Normal lymph nodes. No tongue deviation. Normal balance test. Normal strength and movement in upper and lower extremities, symmetrical. Previous CT/MRI from 5 years ago was normal per patient.
260
+
261
+ **A – Assessment**
262
+ Migraine with aura, unresponsive to current medication (Imitrex).
263
+
264
+ **P – Plan**
265
+ 1. Trial of new triptan medication (Rizatriptan) and oxygen therapy
266
+ 2. Prescription for Rizatriptan
267
+ 3. Recommendation to maintain migraine diary to identify triggers
268
+ 4. Follow up with primary care physician"""
269
 
270
 
271
+ # Create Gradio interface
272
+ with gr.Blocks() as demo:
273
+ gr.Markdown("# Clinical SOAP Note Generator")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
+ if not LLAMA_CPP_AVAILABLE:
276
+ gr.Markdown("""
277
+ ## ⚠️ Important: llama-cpp-python Not Installed
 
 
 
278
 
279
+ This application requires the llama-cpp-python library. Please install it:
 
280
 
281
+ ```bash
282
+ pip install llama-cpp-python
283
+ ```
284
 
285
+ The interface below will operate in demo mode only.
286
+ """)
287
+ else:
288
+ gr.Markdown("""
289
+ ## GGUF Model-based SOAP Note Generator
290
 
291
+ This app uses lightweight GGUF models via llama.cpp to generate SOAP notes from doctor-patient transcripts.
292
+ Models will be loaded when you first generate a note.
293
+ """)
 
294
 
295
  with gr.Row():
296
  with gr.Column():
297
  prompt_template = gr.Textbox(
298
  label="Prompt Template",
299
  placeholder="Enter SOAP note generation prompt/instructions here...",
300
+ lines=10,
301
+ value="""**Role:** You are an AI assistant specialized in generating clinical SOAP notes.
302
+
303
+ **Task:** Generate a concise, accurate, and clinically relevant SOAP note based **STRICTLY AND SOLELY** on the provided doctor-patient interaction transcript.
304
+
305
+ **CRITICAL INSTRUCTIONS:**
306
+
307
+ 1. **Strict Transcript Adherence:** Generate the SOAP note using **ONLY** information **explicitly stated** within the provided transcript.
308
+ 2. **NO Assumptions or External Knowledge:** **DO NOT** infer information, add details not mentioned (even if clinically likely), make assumptions, or use external medical knowledge. Adherence to the transcript is paramount.
309
+ 3. **Standard SOAP Structure:** Organize the output clearly into the following sections using **EXACTLY** these headings:
310
+ * **S – Subjective**
311
+ * **O – Objective**
312
+ * **A – Assessment**
313
+ * **P – Plan**
314
+ 4. **NO Extraneous Text:** The output must contain **ONLY** the four section headings (S, O, A, P) and the corresponding content derived *directly* from the transcript. **DO NOT** include introductory sentences (e.g., "Here is the SOAP note:"), concluding remarks, disclaimers, notes about the generation process, metadata, or *any* other text before, between, or after the S/O/A/P sections."""
315
  )
316
  transcript = gr.Textbox(
317
  label="Doctor-Patient Transcript",
 
345
  generate_btn = gr.Button("Generate SOAP Notes", variant="primary")
346
 
347
  with gr.Column():
348
+ with gr.Tab("Base Model (Qwen3-4B-GGUF)"):
349
+ base_thinking = gr.Textbox(label="Model Process", lines=3, interactive=False)
350
  base_content = gr.Textbox(label="Base Model SOAP Note", lines=20, interactive=False)
351
 
352
+ with gr.Tab("Novel Model (Clinician-Note-2.0a-GGUF)"):
353
+ novel_thinking = gr.Textbox(label="Model Process", lines=3, interactive=False)
354
  novel_content = gr.Textbox(label="Novel Model SOAP Note", lines=20, interactive=False)
355
 
356
  # Connect the generate button
357
  generate_btn.click(
358
  generate_soap_notes,
359
  inputs=[transcript, prompt_template, temperature, top_p, top_k],
360
+ outputs=[base_thinking, base_content, novel_thinking, novel_content]
361
  )
362
 
363
  # Add example inputs
 
518
  Lisa: No, I'm just ready to go back home.
519
 
520
  Doctor: Sounds like a good plan. All right Lisa, take care and feel better.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
  """
522
  ]
523
  ],
524
+ inputs=[transcript]
525
  )
526
 
527
  if __name__ == "__main__":
requirements.txt CHANGED
@@ -4,4 +4,5 @@ gradio==5.29.0
4
  torch==2.7.0
5
  accelerate==1.6.0
6
  sentencepiece==0.2.0
7
- protobuf==6.30.2
 
 
4
  torch==2.7.0
5
  accelerate==1.6.0
6
  sentencepiece==0.2.0
7
+ protobuf==6.30.2
8
+ llama-cpp-python==0.3.9