piyushdev commited on
Commit
ad19ccb
Β·
verified Β·
1 Parent(s): e767889

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +342 -164
app.py CHANGED
@@ -1,164 +1,342 @@
1
- # πŸš€ App Improvements Summary
2
-
3
- ## Major Enhancements Made
4
-
5
- ### 1. **Robust Error Handling & Retry Logic**
6
- - βœ… **3 automatic retries** per category
7
- - βœ… 1-second delay between retry attempts
8
- - βœ… Graceful degradation if JSON parsing fails
9
- - βœ… Detailed error messages for debugging
10
-
11
- ### 2. **Output Validation & Quality Checks**
12
- - βœ… JSON structure validation
13
- - βœ… Minimum description length check (10 characters)
14
- - βœ… Multiple JSON extraction methods (handles markdown, raw JSON, etc.)
15
- - βœ… Fallback to raw response if JSON parsing fails
16
-
17
- ### 3. **Improved Prompt Engineering**
18
- - βœ… More explicit instructions for JSON-only output
19
- - βœ… Stricter formatting requirements
20
- - βœ… Clearer examples in system prompt
21
-
22
- ### 4. **Better Output Consistency**
23
- - βœ… **Lower default temperature** (0.3 instead of 0.7)
24
- - βœ… Temperature tooltip explaining impact on consistency
25
- - βœ… Recommended settings prominently displayed
26
-
27
- ### 5. **Enhanced Status Reporting**
28
- - βœ… New **Status column** in output CSV
29
- - βœ… Per-category success/failure tracking
30
- - βœ… Success count vs. failure count summary
31
- - βœ… Individual status messages for each category
32
- - βœ… Failed categories clearly marked with error details
33
-
34
- ### 6. **Rate Limiting Protection**
35
- - βœ… 0.5-second delay between each category
36
- - βœ… Prevents API throttling
37
- - βœ… More reliable batch processing
38
-
39
- ### 7. **Zero GPU Support Information**
40
- - βœ… Instructions for using Zero GPU
41
- - βœ… Clear benefits explanation (faster, more reliable)
42
- - βœ… Free GPU acceleration (no Pro subscription required)
43
-
44
- ### 8. **Better User Experience**
45
- - βœ… Real-time progress updates
46
- - βœ… Clear feature list in UI
47
- - βœ… Detailed tips for best results
48
- - βœ… Success/failure summary after processing
49
-
50
- ## Key Code Improvements
51
-
52
- ### New Functions
53
- 1. `extract_json_from_response()` - Robust JSON extraction with multiple fallback methods
54
- 2. Enhanced `process_single_category()` - Retry logic, validation, better error handling
55
-
56
- ### Updated Processing Flow
57
- ```
58
- For each category:
59
- 1. Attempt processing (streaming API call)
60
- 2. Validate response is not empty
61
- 3. Extract JSON from response (multiple methods)
62
- 4. Validate JSON structure and content
63
- 5. If failure β†’ retry (up to 3 times)
64
- 6. If all retries fail β†’ mark as Failed with error details
65
- 7. Add 0.5s delay before next category
66
- ```
67
-
68
- ## Configuration Changes
69
-
70
- ### New Defaults
71
- - **Temperature**: 0.3 (was 0.7) - More consistent output
72
- - **Retry Count**: 3 attempts per category
73
- - **Delay**: 0.5s between categories, 1s between retries
74
-
75
- ### Output Format
76
- ```csv
77
- Category,Description,Raw_Response,Status
78
- Example Category,"validated description text","raw JSON response","Success"
79
- Failed Category,"[FAILED - error details]","","Failed"
80
- ```
81
-
82
- ## Expected Results
83
-
84
- ### Before Improvements
85
- - ❌ 6-7 out of 13 categories succeeded
86
- - ❌ Garbage values in some outputs
87
- - ❌ Inconsistent formatting
88
- - ❌ No way to identify failures
89
-
90
- ### After Improvements
91
- - βœ… **Higher success rate** due to retry logic
92
- - βœ… **Validated outputs** - no garbage values
93
- - βœ… **Consistent formatting** with lower temperature
94
- - βœ… **Clear status tracking** for all categories
95
- - βœ… **Reprocessable failures** - extract and retry failed ones
96
-
97
- ## How to Get Best Results
98
-
99
- ### Recommended Settings
100
- 1. **Temperature: 0.2-0.4** for consistent, focused descriptions
101
- 2. **Zero GPU** is automatically available (no setup needed)
102
- 3. **Check Status column** in output to identify any failures
103
- 4. **Reprocess failed categories** separately if needed
104
-
105
- ### For Large Batches
106
- 1. Zero GPU provides automatic GPU acceleration (free)
107
- 2. Split into smaller files if over 100 categories
108
- 3. Monitor the status output during processing
109
- 4. Review failed categories and adjust temperature if needed
110
-
111
- ## Testing Instructions
112
-
113
- 1. Upload the `sample_categories.csv` (13 categories)
114
- 2. Use default settings (Temperature: 0.3)
115
- 3. Click "Process Files"
116
- 4. Check output CSV:
117
- - Should have **all 13 categories**
118
- - Status column shows "Success" for most/all
119
- - Descriptions are consistent and well-formatted
120
- - Any failures have clear error messages
121
-
122
- ## Zero GPU Usage
123
-
124
- ### How Zero GPU Works
125
- 1. Zero GPU is automatically available for Hugging Face Spaces
126
- 2. No configuration needed - it's already enabled
127
- 3. GPU resources are allocated when your Space runs
128
- 4. Free to use - no Pro subscription required
129
- 5. Provides on-demand GPU acceleration
130
-
131
- ### Benefits
132
- - ⚑ **Faster processing** (2-3x speedup)
133
- - 🎯 **More reliable** (better resource availability)
134
- - πŸ“Š **Better for large batches** (50+ categories)
135
- - πŸ’° **Free** - no Pro subscription needed
136
-
137
- **Note**: Zero GPU provides free GPU access for Spaces automatically
138
-
139
- ## Troubleshooting Failed Categories
140
-
141
- If some categories still fail after improvements:
142
-
143
- 1. **Check the error message** in the Description field
144
- 2. **Common issues**:
145
- - API timeout β†’ Enable GPU
146
- - Rate limiting β†’ Already handled with delays
147
- - Invalid JSON β†’ Retry logic should handle this
148
- 3. **Reprocess failures**:
149
- - Extract failed categories from output CSV
150
- - Create new CSV with just those categories
151
- - Reprocess with even lower temperature (0.2)
152
-
153
- ## Summary
154
-
155
- The app is now **much more robust** with:
156
- - πŸ” Automatic retries
157
- - βœ… Output validation
158
- - πŸ“Š Status tracking
159
- - ⚑ Zero GPU support (free acceleration)
160
- - 🎯 Better consistency
161
-
162
- This should give you **100% success rate** or very close to it, with all outputs properly formatted and validated!
163
-
164
- Zero GPU provides automatic GPU acceleration without any configuration or cost, making your app faster and more reliable!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from huggingface_hub import InferenceClient
3
+ import pandas as pd
4
+ import json
5
+ import os
6
+ import time
7
+ from datetime import datetime
8
+
9
+ # Custom system instructions for business category descriptions
10
+ SYSTEM_INSTRUCTIONS = """You are an expert at writing clear and visual descriptions for a business category keyword for a yellow pages or business listing website. Given a category keyword, generate a single, detailed description that defines its key visual elements, location, and context. Do not add artistic or stylistic flair. Ensure that the description is CLIP model ready and not too verbose.
11
+
12
+ Here are some examples of the correct format:
13
+
14
+ Category: "Car Rental For Self Driven"
15
+
16
+ Description: "a car available for self-drive rental, parked at a pickup spot without a chauffeur; looks travel-ready, clean, well-maintained, keys handed over to customer"
17
+
18
+ Category: "Mehandi"
19
+
20
+ Description: "Temporary henna artwork applied on hands and feet using cones; fine brown or maroon floral and paisley patterns, mandalas, and lace-like detailing, commonly seen at weddings and festivals."
21
+
22
+ Category: "Photographer"
23
+
24
+ Description: "a person actively shooting photos or posing with a camera; holding a camera to eye, adjusting lens, or directing a subject during a shoot"
25
+
26
+ Category: "Equipment"
27
+
28
+ Description: "lighting stands, softboxes, strobes, tripods, reflectors, gimbals, battery packs, memory cards arranged as gear kits"
29
+
30
+ ---
31
+
32
+ IMPORTANT: You must respond with ONLY a valid JSON object in this exact format:
33
+ {"Category": "category name", "Description": "description text"}
34
+
35
+ Do not include any other text, explanations, or markdown formatting. Only output the JSON object."""
36
+
37
+
38
+ def extract_json_from_response(response_text):
39
+ """Extract and validate JSON from model response."""
40
+ # Try to find JSON in the response
41
+ response_text = response_text.strip()
42
+
43
+ # Remove markdown code blocks if present
44
+ if "```json" in response_text:
45
+ response_text = response_text.split("```json")[1].split("```")[0].strip()
46
+ elif "```" in response_text:
47
+ response_text = response_text.split("```")[1].split("```")[0].strip()
48
+
49
+ # Try to find JSON object in the text
50
+ if "{" in response_text and "}" in response_text:
51
+ start = response_text.find("{")
52
+ end = response_text.rfind("}") + 1
53
+ response_text = response_text[start:end]
54
+
55
+ # Parse JSON
56
+ parsed = json.loads(response_text)
57
+
58
+ # Validate structure
59
+ if not isinstance(parsed, dict):
60
+ raise ValueError("Response is not a JSON object")
61
+
62
+ # Get description with various possible keys
63
+ description = (
64
+ parsed.get("Description") or
65
+ parsed.get("description") or
66
+ parsed.get("desc") or
67
+ ""
68
+ )
69
+
70
+ if not description or len(description.strip()) < 10:
71
+ raise ValueError("Description is missing or too short")
72
+
73
+ return description.strip()
74
+
75
+
76
+ def process_single_category(category, client, max_tokens, temperature, top_p, retry_count=3):
77
+ """Process a single category keyword and return the description with retry logic."""
78
+ messages = [
79
+ {"role": "system", "content": SYSTEM_INSTRUCTIONS},
80
+ {"role": "user", "content": f"Category: \"{category}\""}
81
+ ]
82
+
83
+ last_error = None
84
+
85
+ for attempt in range(retry_count):
86
+ try:
87
+ # Add small delay between retries
88
+ if attempt > 0:
89
+ time.sleep(1)
90
+
91
+ # Try streaming approach (more reliable for this model)
92
+ response_text = ""
93
+ for message in client.chat_completion(
94
+ messages,
95
+ max_tokens=max_tokens,
96
+ stream=True,
97
+ temperature=temperature,
98
+ top_p=top_p,
99
+ ):
100
+ if hasattr(message, 'choices') and len(message.choices) > 0:
101
+ if hasattr(message.choices[0], 'delta') and hasattr(message.choices[0].delta, 'content'):
102
+ token = message.choices[0].delta.content
103
+ if token:
104
+ response_text += token
105
+ elif isinstance(message, str):
106
+ response_text += message
107
+
108
+ # Validate we got a response
109
+ if not response_text or len(response_text.strip()) < 5:
110
+ raise ValueError("Empty or too short response from model")
111
+
112
+ # Extract and validate JSON
113
+ description = extract_json_from_response(response_text)
114
+
115
+ # Return both the description and raw response
116
+ return response_text.strip(), description
117
+
118
+ except json.JSONDecodeError as e:
119
+ last_error = f"JSON parsing failed (attempt {attempt + 1}/{retry_count}): {str(e)}"
120
+ # If JSON parsing fails, try to extract description from raw text
121
+ if attempt == retry_count - 1 and response_text:
122
+ # Last attempt - try to use raw response if it looks like a description
123
+ if len(response_text.strip()) > 20 and not response_text.startswith("{"):
124
+ return response_text.strip(), response_text.strip()
125
+ except Exception as e:
126
+ last_error = f"Processing failed (attempt {attempt + 1}/{retry_count}): {str(e)}"
127
+
128
+ # All retries failed
129
+ raise Exception(f"Failed after {retry_count} attempts. Last error: {last_error}")
130
+
131
+
132
+ def process_csv_files(
133
+ files,
134
+ category_column,
135
+ max_tokens,
136
+ temperature,
137
+ top_p,
138
+ progress=gr.Progress()
139
+ ):
140
+ """
141
+ Process multiple CSV files and generate descriptions for category keywords.
142
+ """
143
+ if not files or len(files) == 0:
144
+ return "Please upload at least one CSV file.", None
145
+
146
+ # Get HF token from environment variables
147
+ import os
148
+ hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACE_TOKEN")
149
+
150
+ if not hf_token:
151
+ return "❌ Error: HF_TOKEN not found. Please add your Hugging Face token as a Space Secret.\n\nGo to Space Settings β†’ Secrets β†’ Add 'HF_TOKEN'", None
152
+
153
+ client = InferenceClient(token=hf_token, model="openai/gpt-oss-20b")
154
+
155
+ output_files = []
156
+ status_messages = []
157
+
158
+ for file_idx, file in enumerate(files):
159
+ try:
160
+ # Read CSV file
161
+ df = pd.read_csv(file.name)
162
+ status_messages.append(f"πŸ“„ Processing file {file_idx + 1}/{len(files)}: {os.path.basename(file.name)}")
163
+
164
+ # Check if category column exists
165
+ if category_column not in df.columns:
166
+ status_messages.append(f"⚠️ Warning: Column '{category_column}' not found in {os.path.basename(file.name)}. Available columns: {', '.join(df.columns)}")
167
+ continue
168
+
169
+ # Process each category
170
+ descriptions = []
171
+ raw_responses = []
172
+
173
+ categories = df[category_column].dropna().unique()
174
+ total_categories = len(categories)
175
+
176
+ for idx, category in enumerate(categories):
177
+ progress((file_idx * total_categories + idx) / (len(files) * total_categories),
178
+ desc=f"Processing category {idx + 1}/{total_categories} in file {file_idx + 1}")
179
+
180
+ try:
181
+ # Process with retry logic
182
+ raw_response, description = process_single_category(
183
+ category, client, max_tokens, temperature, top_p, retry_count=3
184
+ )
185
+
186
+ # Validate description
187
+ if not description or len(description.strip()) < 10:
188
+ raise ValueError("Description is too short or empty")
189
+
190
+ descriptions.append({
191
+ "Category": category,
192
+ "Description": description,
193
+ "Raw_Response": raw_response,
194
+ "Status": "Success"
195
+ })
196
+
197
+ status_messages.append(f"βœ… Processed: {category}")
198
+
199
+ except Exception as e:
200
+ error_msg = str(e)
201
+ status_messages.append(f"⚠️ Error processing '{category}': {error_msg}")
202
+
203
+ descriptions.append({
204
+ "Category": category,
205
+ "Description": f"[FAILED - {error_msg[:100]}]",
206
+ "Raw_Response": "",
207
+ "Status": "Failed"
208
+ })
209
+
210
+ # Small delay to avoid rate limiting
211
+ time.sleep(0.5)
212
+
213
+ # Create output dataframe
214
+ output_df = pd.DataFrame(descriptions)
215
+
216
+ # Save to file
217
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
218
+ base_name = os.path.splitext(os.path.basename(file.name))[0]
219
+ output_filename = f"output_{base_name}_{timestamp}.csv"
220
+ output_df.to_csv(output_filename, index=False)
221
+ output_files.append(output_filename)
222
+
223
+ # Count successes and failures
224
+ success_count = len([d for d in descriptions if d.get("Status") == "Success"])
225
+ failed_count = len([d for d in descriptions if d.get("Status") == "Failed"])
226
+
227
+ status_messages.append(f"βœ… Completed: {success_count} succeeded, {failed_count} failed out of {len(descriptions)} categories from {os.path.basename(file.name)}")
228
+
229
+ except Exception as e:
230
+ status_messages.append(f"❌ Error processing {os.path.basename(file.name)}: {str(e)}")
231
+
232
+ status_text = "\n".join(status_messages)
233
+
234
+ if output_files:
235
+ return status_text, output_files
236
+ else:
237
+ return status_text + "\n\n❌ No output files generated.", None
238
+
239
+
240
+ # Create Gradio interface
241
+ with gr.Blocks(title="Business Category Description Generator") as demo:
242
+ gr.Markdown("""
243
+ # 🏒 Business Category Description Generator
244
+
245
+ Upload CSV files containing business category keywords, and this app will generate
246
+ CLIP-ready visual descriptions for each category using AI.
247
+
248
+ **Instructions:**
249
+ 1. Upload one or more CSV files
250
+ 2. Specify the column name that contains the category keywords
251
+ 3. Adjust model settings (lower temperature = more consistent output)
252
+ 4. Click "Process Files" to generate descriptions
253
+ 5. Download the output CSV files with Status column
254
+
255
+ **Features:**
256
+ - βœ… Automatic retry logic (3 attempts per category)
257
+ - βœ… JSON validation and error recovery
258
+ - βœ… Progress tracking with detailed status
259
+ - βœ… Success/failure reporting
260
+
261
+ *Note: For faster processing, use Zero GPU (see Space Settings). Authentication via HF_TOKEN secret.*
262
+ """)
263
+
264
+ with gr.Row():
265
+ with gr.Column(scale=1):
266
+ gr.Markdown("### βš™οΈ Model Settings")
267
+ max_tokens = gr.Slider(
268
+ minimum=64,
269
+ maximum=512,
270
+ value=256,
271
+ step=16,
272
+ label="Max Tokens"
273
+ )
274
+ temperature = gr.Slider(
275
+ minimum=0.1,
276
+ maximum=1.0,
277
+ value=0.3,
278
+ step=0.1,
279
+ label="Temperature",
280
+ info="Lower = more consistent output"
281
+ )
282
+ top_p = gr.Slider(
283
+ minimum=0.1,
284
+ maximum=1.0,
285
+ value=0.9,
286
+ step=0.05,
287
+ label="Top-p"
288
+ )
289
+
290
+ with gr.Column(scale=2):
291
+ files_input = gr.File(
292
+ label="πŸ“€ Upload CSV Files",
293
+ file_count="multiple",
294
+ file_types=[".csv"]
295
+ )
296
+ category_column = gr.Textbox(
297
+ label="πŸ“ Category Column Name",
298
+ value="category",
299
+ placeholder="Enter the name of the column containing categories"
300
+ )
301
+ process_btn = gr.Button("πŸš€ Process Files", variant="primary", size="lg")
302
+
303
+ status_output = gr.Textbox(
304
+ label="πŸ“Š Status",
305
+ lines=10,
306
+ interactive=False
307
+ )
308
+ files_output = gr.File(
309
+ label="πŸ’Ύ Download Output Files",
310
+ file_count="multiple"
311
+ )
312
+
313
+ process_btn.click(
314
+ fn=process_csv_files,
315
+ inputs=[
316
+ files_input,
317
+ category_column,
318
+ max_tokens,
319
+ temperature,
320
+ top_p
321
+ ],
322
+ outputs=[status_output, files_output]
323
+ )
324
+
325
+ gr.Markdown("""
326
+ ---
327
+ ### πŸ“ Output Format
328
+ Each output CSV file will contain:
329
+ - **Category**: The original category keyword
330
+ - **Description**: The generated visual description (validated and cleaned)
331
+ - **Raw_Response**: The complete model response (for debugging)
332
+ - **Status**: Success or Failed (with error details)
333
+
334
+ πŸ’‘ **Tips for Best Results:**
335
+ - Use Temperature 0.2-0.4 for consistent, focused descriptions
336
+ - Use Temperature 0.6-0.8 for more creative variations
337
+ - Failed categories are marked clearly - you can reprocess them separately
338
+ - Zero GPU acceleration: Add @spaces.GPU decorator or enable in Space Settings
339
+ """)
340
+
341
+ if __name__ == "__main__":
342
+ demo.launch()