WillHeld commited on
Commit
5cd2aa6
Β·
verified Β·
1 Parent(s): f5ef4f1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +198 -191
app.py CHANGED
@@ -1,218 +1,225 @@
1
- import spaces
2
- from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
3
- import gradio as gr
4
- from threading import Thread
5
- import os
 
 
 
 
 
 
 
 
6
  import json
7
- import uuid
8
- from datasets import Dataset
9
- from huggingface_hub import HfApi, login
10
  import time
 
 
11
 
12
- # Install required packages if not present
 
13
  from gradio_modal import Modal
14
- import huggingface_hub
15
- import datasets
 
 
 
 
 
 
16
 
17
- # Model setup
18
  checkpoint = "marin-community/marin-8b-instruct"
19
- device = "cuda"
 
 
20
  tokenizer = AutoTokenizer.from_pretrained(checkpoint)
21
  model = AutoModelForCausalLM.from_pretrained(checkpoint).to(device)
22
 
23
- # Constants for dataset
24
- DATASET_REPO = "WillHeld/model-feedback" # Replace with your username
25
- DATASET_PATH = "./feedback_data" # Local path to store feedback
26
- DATASET_FILENAME = "feedback.jsonl" # Filename for feedback data
27
-
28
- # Ensure feedback directory exists
29
- os.makedirs(DATASET_PATH, exist_ok=True)
30
-
31
- # Feedback storage functions
32
- def save_feedback_locally(conversation, satisfaction, feedback_text):
33
- """Save feedback to a local JSONL file"""
34
- # Create a unique ID for this feedback entry
35
- feedback_id = str(uuid.uuid4())
36
-
37
- # Create a timestamp
38
- timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
39
-
40
- # Prepare the feedback data
41
- feedback_data = {
42
- "id": feedback_id,
43
- "timestamp": timestamp,
44
  "conversation": conversation,
45
  "satisfaction": satisfaction,
46
- "feedback": feedback_text
47
  }
48
-
49
- # Save to local file
50
- feedback_file = os.path.join(DATASET_PATH, DATASET_FILENAME)
51
- with open(feedback_file, "a") as f:
52
- f.write(json.dumps(feedback_data) + "\n")
53
-
54
- return feedback_id
55
-
56
- def push_feedback_to_hub(hf_token=None):
57
- """Push the local feedback data to HuggingFace as a dataset"""
58
- # Check if we have a token
59
- if hf_token is None:
60
- # Try to get token from environment variable
61
- hf_token = os.environ.get("HF_TOKEN")
62
- if hf_token is None:
63
- print("No HuggingFace token provided. Cannot push to Hub.")
64
- return False
65
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  try:
67
- # Login to HuggingFace
68
- login(token=hf_token)
69
-
70
- # Check if we have data to push
71
- feedback_file = os.path.join(DATASET_PATH, DATASET_FILENAME)
72
- if not os.path.exists(feedback_file):
73
- print("No feedback data to push.")
74
- return False
75
-
76
- # Load data from the JSONL file
77
- with open(feedback_file, "r") as f:
78
- feedback_data = [json.loads(line) for line in f]
79
-
80
- # Create a dataset from the feedback data
81
- dataset = Dataset.from_list(feedback_data)
82
-
83
- # Push to Hub
84
- dataset.push_to_hub(
85
  DATASET_REPO,
86
- private=True # Set to False if you want the dataset to be public
 
 
87
  )
88
-
89
- print(f"Feedback data pushed to {DATASET_REPO} successfully.")
90
- return True
91
-
92
- except Exception as e:
93
- print(f"Error pushing feedback data to Hub: {e}")
94
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
- # Modified predict function to update conversation state
97
  @spaces.GPU(duration=120)
98
- def predict(message, history, state, temperature, top_p):
99
- # Update history with user message
 
 
 
 
 
100
  history.append({"role": "user", "content": message})
101
-
102
-
103
- input_text = tokenizer.apply_chat_template(history, tokenize=False, add_generation_prompt=True)
104
- inputs = tokenizer.encode(input_text, return_tensors="pt").to(device)
105
-
106
- # Create a streamer
107
- streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
108
-
109
- # Set up generation parameters
110
- generation_kwargs = {
111
- "input_ids": inputs,
112
- "max_new_tokens": 1024,
113
- "temperature": float(temperature),
114
- "top_p": float(top_p),
115
- "do_sample": True,
116
- "streamer": streamer,
117
- }
118
-
119
- # Run generation in a separate thread
120
- thread = Thread(target=model.generate, kwargs=generation_kwargs)
121
- thread.start()
122
-
123
- # Yield from the streamer as tokens are generated
124
- partial_text = ""
125
- for new_text in streamer:
126
- partial_text += new_text
127
- yield partial_text, state
128
-
129
- # After full generation, update state with assistant's response
130
- history.append({"role": "assistant", "content": partial_text})
131
- state = history.copy()
132
- return partial_text, state
133
-
134
- # Function to handle the research feedback submission
135
- def submit_research_feedback(conversation_state, satisfaction, feedback_text):
136
- """Save user feedback both locally and to HuggingFace Hub"""
137
- # Save locally first
138
- feedback_id = save_feedback_locally(conversation_state, satisfaction, feedback_text)
139
-
140
- # Get token from environment variable
141
- env_token = os.environ.get("HF_TOKEN")
142
-
143
- # Use environment token
144
- push_success = push_feedback_to_hub(env_token)
145
-
146
- if push_success:
147
- status_msg = "Thank you for your valuable feedback! Your insights have been saved to the dataset."
148
- else:
149
- status_msg = "Thank you for your feedback! It has been saved locally, but couldn't be pushed to the dataset. Please check server logs."
150
-
151
- return status_msg
152
-
153
- # Create the Gradio blocks interface
154
- with gr.Blocks() as demo:
155
- # State to track conversation history
156
  conversation_state = gr.State([])
157
-
158
  with gr.Row():
 
159
  with gr.Column(scale=3):
160
- # Custom chat function wrapper to update state
161
- def chat_with_state(message, history, state, temperature, top_p):
162
- for partial_response, updated_state in predict(message, history, state, temperature, top_p):
163
- # Update our state with each yield
164
- state = updated_state
165
- yield partial_response, state
166
-
167
- # Create ChatInterface
168
  chatbot = gr.ChatInterface(
169
- chat_with_state,
170
- additional_inputs=[
171
- conversation_state,
172
- gr.Slider(0.1, 2.0, value=0.7, step=0.1, label="Temperature"),
173
- gr.Slider(0.1, 1.0, value=0.9, step=0.05, label="Top-P")
174
- ],
175
  additional_outputs=[conversation_state],
176
- type="messages"
177
  )
178
-
 
179
  with gr.Column(scale=1):
180
- report_button = gr.Button("Share Feedback", variant="primary")
181
-
182
- # Create the modal with feedback form components
183
- with Modal(visible=False) as feedback_modal:
184
- with gr.Column():
185
- gr.Markdown("## Research Preview Feedback")
186
- gr.Markdown("Thank you for testing our research model. Your feedback (positive or negative) helps us improve!")
187
-
188
- satisfaction = gr.Radio(
189
- ["Very satisfied", "Satisfied", "Neutral", "Unsatisfied", "Very unsatisfied"],
190
- label="How would you rate your experience with this research model?",
191
- value="Neutral"
192
- )
193
-
194
- feedback_text = gr.Textbox(
195
- lines=5,
196
- label="Share your observations (strengths, weaknesses, suggestions):",
197
- placeholder="We welcome both positive feedback and constructive criticism to help improve this research prototype..."
198
- )
199
-
200
- submit_button = gr.Button("Submit Research Feedback", variant="primary")
201
- response_text = gr.Textbox(label="Status", interactive=False)
202
-
203
- # Connect the "Share Feedback" button to show the modal
204
- report_button.click(
205
- lambda: Modal(visible=True),
206
- None,
207
- feedback_modal
208
- )
209
-
210
- # Connect the submit button to the submit_research_feedback function with the current conversation state
211
- submit_button.click(
212
- submit_research_feedback,
213
- inputs=[conversation_state, satisfaction, feedback_text],
214
- outputs=response_text
215
  )
216
 
217
- # Launch the demo
218
- demo.launch()
 
 
1
+ #!/usr/bin/env python
2
+ """HFΒ Space for the *Marin‑8B‑Instruct* research preview
3
+ -----------------------------------------------------
4
+ A lightweight Gradio interface that
5
+ β€’ streams chat completions from the `marin-community/marin-8b-instruct` model
6
+ β€’ lets testers submit structured feedback (UX ratingΒ + free‑text)
7
+ β€’ appends feedback to a local JSONL *and* merges it into a private Hub dataset
8
+ The dataset is never overwritten: we always pull, merge, deduplicate, and push.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ # ‑‑ standard lib
14
  import json
15
+ import os
 
 
16
  import time
17
+ import uuid
18
+ from threading import Thread
19
 
20
+ # ‑‑ third‑party deps (declared in requirements.txt of the Space)
21
+ import gradio as gr
22
  from gradio_modal import Modal
23
+ from transformers import (
24
+ AutoModelForCausalLM,
25
+ AutoTokenizer,
26
+ TextIteratorStreamer,
27
+ )
28
+ from datasets import Dataset, load_dataset, concatenate_datasets, DownloadMode
29
+ from huggingface_hub import HfApi, login
30
+ import spaces
31
 
32
+ # ──────────────────────────── modelΒ & constants ─────────────────────────────
33
  checkpoint = "marin-community/marin-8b-instruct"
34
+ device = "cuda" # the Space runner gives us a GPU
35
+
36
+ # download πŸ”₯
37
  tokenizer = AutoTokenizer.from_pretrained(checkpoint)
38
  model = AutoModelForCausalLM.from_pretrained(checkpoint).to(device)
39
 
40
+ # feedbackΒ dataset details
41
+ DATASET_REPO = "WillHeld/model-feedback" # <‑‑ change to your namespace if needed
42
+ DATA_DIR = "./feedback_data"
43
+ DATA_FILE = "feedback.jsonl"
44
+ os.makedirs(DATA_DIR, exist_ok=True)
45
+
46
+ # ──────────────────────────── helpers ───────────────────────────────────────
47
+
48
+ def save_feedback_locally(conversation: list[dict[str, str]],
49
+ satisfaction: str,
50
+ feedback_text: str) -> str:
51
+ """Append a single feedback record to a JSONL file and return its UUID."""
52
+ record = {
53
+ "id": str(uuid.uuid4()),
54
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
 
 
 
 
 
 
55
  "conversation": conversation,
56
  "satisfaction": satisfaction,
57
+ "feedback": feedback_text,
58
  }
59
+ fp = os.path.join(DATA_DIR, DATA_FILE)
60
+ with open(fp, "a", encoding="utf‑8") as f:
61
+ f.write(json.dumps(record, ensure_ascii=False) + "\n")
62
+ return record["id"]
63
+
64
+
65
+ def push_feedback_to_hub(hf_token: str | None = None) -> bool: # noqa: C901
66
+ """Merge freshly collected feedback with what’s already on the Hub.
67
+
68
+ Steps
69
+ -----
70
+ 1. Authenticate with `hf_token` (fall back to $HF_TOKEN env).
71
+ 2. Load *local* feedback just written in `feedback.jsonl`.
72
+ 3. Pull existing remote split (if any); concat & `unique("id")`.
73
+ 4. Push the merged dataset back.Β Never deletes remote shards β‡’ safe.
74
+ """
75
+
76
+ hf_token = hf_token or os.getenv("HF_TOKEN")
77
+ if not hf_token:
78
+ print("❌ No HF token β€” skipping Hub push.")
79
+ return False
80
+ login(token=hf_token)
81
+
82
+ fp = os.path.join(DATA_DIR, DATA_FILE)
83
+ if not os.path.exists(fp):
84
+ print("❌ Local feedback file missing; nothing to push.")
85
+ return False
86
+
87
+ # local rows β†’ Dataset
88
+ with open(fp, encoding="utf‑8") as f:
89
+ local_ds = Dataset.from_list([json.loads(l) for l in f])
90
+
91
+ # try to pull remote
92
  try:
93
+ remote_ds = load_dataset(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  DATASET_REPO,
95
+ split="train",
96
+ token=hf_token,
97
+ download_mode=DownloadMode.FORCE_REDOWNLOAD,
98
  )
99
+ merged = concatenate_datasets([remote_ds, local_ds]).unique("id")
100
+ except FileNotFoundError:
101
+ # repo exists but empty
102
+ merged = local_ds
103
+ except Exception:
104
+ # repo may not exist yet – create & start fresh
105
+ HfApi(token=hf_token).create_repo(
106
+ repo_id=DATASET_REPO, repo_type="dataset", private=True
107
+ )
108
+ merged = local_ds
109
+
110
+ merged.push_to_hub(
111
+ DATASET_REPO,
112
+ private=True,
113
+ commit_message=f"Add {len(local_ds)} new feedback entries",
114
+ )
115
+ print(
116
+ f"βœ… Pushed {len(local_ds)} rows; dataset now has {len(merged)} total.")
117
+ # (optional) clear local file once synced
118
+ # os.remove(fp)
119
+ return True
120
+
121
+ # ──────────────────────────── chat backend ─────────────────────────────────
122
 
 
123
  @spaces.GPU(duration=120)
124
+ def generate_response(message: str,
125
+ history: list[dict[str, str]],
126
+ temperature: float,
127
+ top_p: float):
128
+ """Streaming generator used by the Gradio ChatInterface."""
129
+
130
+ # 1) add user message to history
131
  history.append({"role": "user", "content": message})
132
+
133
+ # 2) build model input via chat template
134
+ prompt = tokenizer.apply_chat_template(history, tokenize=False,
135
+ add_generation_prompt=True)
136
+ input_ids = tokenizer.encode(prompt, return_tensors="pt").to(device)
137
+
138
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True,
139
+ skip_special_tokens=True)
140
+
141
+ gen_kwargs = dict(
142
+ input_ids=input_ids,
143
+ max_new_tokens=1024,
144
+ temperature=float(temperature),
145
+ top_p=float(top_p),
146
+ do_sample=True,
147
+ streamer=streamer,
148
+ )
149
+
150
+ # run on a worker thread so we can yield tokens live
151
+ Thread(target=model.generate, kwargs=gen_kwargs).start()
152
+
153
+ partial = ""
154
+ for token in streamer:
155
+ partial += token
156
+ yield partial, history # 1stΒ outΒ = msg, 2ndΒ outΒ = state
157
+
158
+ # once finished, commit assistant reply to history
159
+ history.append({"role": "assistant", "content": partial})
160
+ yield partial, history
161
+
162
+ # ──────────────────────────── feedback handler ─────────────────────────────
163
+
164
+ def submit_feedback(conversation_state: list[dict[str, str]],
165
+ satisfaction: str,
166
+ feedback_text: str):
167
+ """Callback for the *Submit Research Feedback* button."""
168
+ save_feedback_locally(conversation_state, satisfaction, feedback_text)
169
+ pushed = push_feedback_to_hub()
170
+ if pushed:
171
+ return "βœ… Thanks!Β Your feedback is safely stored."
172
+ return "⚠️ Saved locally; Hub push failed. Check server logs."
173
+
174
+ # ──────────────────────────── UI layout ────────────────────────────────────
175
+
176
+ with gr.Blocks(title="Marin‑8B Research Preview") as demo:
177
+ # state object to surface chat history to the feedback form
 
 
 
 
 
 
 
 
 
178
  conversation_state = gr.State([])
179
+
180
  with gr.Row():
181
+ # β€”β€”β€” Chat column β€”β€”β€”
182
  with gr.Column(scale=3):
 
 
 
 
 
 
 
 
183
  chatbot = gr.ChatInterface(
184
+ fn=generate_response,
185
+ additional_inputs=[conversation_state, # keeps state in sync
186
+ gr.Slider(0.1, 2.0, value=0.7, step=0.1,
187
+ label="Temperature"),
188
+ gr.Slider(0.1, 1.0, value=0.9, step=0.05,
189
+ label="Top‑P")],
190
  additional_outputs=[conversation_state],
191
+ type="messages",
192
  )
193
+
194
+ # β€”β€”β€” Sidebar column β€”β€”β€”
195
  with gr.Column(scale=1):
196
+ report_btn = gr.Button("Share Feedback", variant="primary")
197
+
198
+ # feedback modal (hidden by default)
199
+ with Modal(visible=False) as fb_modal:
200
+ gr.Markdown("## Research Preview Feedback")
201
+ gr.Markdown("We appreciate your help improving Marin‑8B! ✨")
202
+
203
+ sat_radio = gr.Radio([
204
+ "Very satisfied", "Satisfied", "Neutral",
205
+ "Unsatisfied", "Very unsatisfied"],
206
+ label="Overall experience",
207
+ value="Neutral",
208
+ )
209
+ fb_text = gr.Textbox(lines=6, label="Comments / suggestions")
210
+ send_btn = gr.Button("Submit", variant="primary")
211
+ status_box = gr.Textbox(label="Status", interactive=False)
212
+
213
+ # interactions
214
+ report_btn.click(lambda: None, None, None, _js="() => window.modal_open()")
215
+ # the JS helper above relies on gradio‑modal’s injected helper.
216
+
217
+ send_btn.click(
218
+ submit_feedback,
219
+ inputs=[conversation_state, sat_radio, fb_text],
220
+ outputs=status_box,
 
 
 
 
 
 
 
 
 
 
221
  )
222
 
223
+ # ──────────────────────────── run! ─────────────────────────────────────────
224
+ if __name__ == "__main__":
225
+ demo.launch()