Luigi commited on
Commit
3d2b701
·
1 Parent(s): 593b6d1

initial commit

Browse files
Files changed (3) hide show
  1. README.md +75 -1
  2. app.py +271 -0
  3. requirements.txt +6 -0
README.md CHANGED
@@ -11,4 +11,78 @@ license: apache-2.0
11
  short_description: NER for Diner Restaurant Reservation with Gemma 3 270m it
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  short_description: NER for Diner Restaurant Reservation with Gemma 3 270m it
12
  ---
13
 
14
+
15
+ # 🍽️ 餐廳訂位資訊提取器
16
+
17
+ 一個基於AI的訂位資訊提取工具,專門用於從中文訊息中自動提取餐廳訂位資訊。
18
+
19
+ ## 功能特色
20
+
21
+ - 🔍 自動從中文訊息提取訂位資訊
22
+ - 📋 輸出結構化的JSON格式
23
+ - 🎯 高準確率的資訊提取
24
+ - 💻 完全基於CPU運行,無需GPU
25
+ - 🎨 現代化的 Gradio v5 界面
26
+
27
+ ## 提取資訊
28
+
29
+ - **👥 人數** (num_people)
30
+ - **📅 預訂日期/時間** (reservation_date)
31
+ - **📞 電話號碼** (phone_num)
32
+
33
+ ## 使用示例
34
+
35
+ 輸入: `"你好,我想訂明天晚上7點的位子,四位成人,電話是0912-345-678"`
36
+
37
+ 輸出:
38
+ ```json
39
+ {
40
+ "num_people": "4",
41
+ "reservation_date": "明天晚上7點",
42
+ "phone_num": "0912-345-678"
43
+ }
44
+ ```
45
+
46
+ ## 技術細節
47
+
48
+ - **模型**: `Luigi/gemma-3-270m-it-dinercall-ner` (基於 Gemma-3-270M 微調)
49
+ - **框架**: Gradio v5 + Transformers
50
+ - **部署**: Hugging Face Spaces (CPU)
51
+
52
+ ## 本地運行
53
+
54
+ ```bash
55
+ pip install -r requirements.txt
56
+ python src/app.py
57
+ ```
58
+
59
+ ## 作者
60
+
61
+ 由 [Together AI](https://together.ai) 提供技術支持
62
+ ```
63
+
64
+ ## Key Features of This Gradio v5 Solution:
65
+
66
+ 1. **Modern UI**: Uses Gradio v5's enhanced styling capabilities
67
+ 2. **Interactive Examples**: Clickable examples that automatically populate the input field
68
+ 3. **Dual Output Display**: Shows both structured JSON and raw model output
69
+ 4. **Statistics Panel**: Displays extracted information in an easy-to-read format
70
+ 5. **Custom CSS**: Enhanced styling with gradients and hover effects
71
+ 6. **Responsive Design**: Works well on both desktop and mobile devices
72
+ 7. **JavaScript Integration**: For smoother interaction with example clicks
73
+
74
+ ## Deployment Instructions:
75
+
76
+ 1. **Create a new Space** on Hugging Face:
77
+ - Go to https://huggingface.co/spaces
78
+ - Click "Create new Space"
79
+ - Select "Gradio" as SDK
80
+ - Name: `dinercall-ner-demo`
81
+ - Visibility: Public
82
+
83
+ 2. **Upload all files** to your Space
84
+
85
+ 3. **The Space will automatically build** and be available at:
86
+ `https://huggingface.co/spaces/your-username/dinercall-ner-demo`
87
+
88
+ This Gradio v5 solution provides a modern, feature-rich interface for your NER model that will work reliably on Hugging Face Spaces without permission issues. The interface is user-friendly and provides all the functionality you need for testing your model.
app.py ADDED
@@ -0,0 +1,271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ from transformers import AutoTokenizer, AutoModelForCausalLM
4
+ import json
5
+ import re
6
+ import time
7
+ from typing import Dict, Any
8
+
9
+ # System prompt (must match training)
10
+ SYSTEM_PROMPT = """你是一個助理,負責從用戶消息中提取預訂資訊並以JSON格式輸出。
11
+ JSON必須包含三個字段: num_people, reservation_date, phone_num。
12
+ 如果某個字段沒有信息,使用空字符串。只輸出JSON,不要添加任何其他文字。"""
13
+
14
+ # Load model and tokenizer with caching
15
+ @gr.cache_resource()
16
+ def load_model():
17
+ """Load the model and tokenizer with caching"""
18
+ try:
19
+ print("Loading model...")
20
+ model_name = "Luigi/gemma-3-270m-it-dinercall-ner"
21
+
22
+ tokenizer = AutoTokenizer.from_pretrained(model_name)
23
+ model = AutoModelForCausalLM.from_pretrained(
24
+ model_name,
25
+ torch_dtype=torch.float32,
26
+ device_map="auto",
27
+ trust_remote_code=True
28
+ )
29
+
30
+ # Set padding token if not set
31
+ if tokenizer.pad_token is None:
32
+ tokenizer.pad_token = tokenizer.eos_token
33
+
34
+ print("Model loaded successfully!")
35
+ return model, tokenizer
36
+ except Exception as e:
37
+ print(f"Error loading model: {e}")
38
+ return None, None
39
+
40
+ # Initialize model
41
+ model, tokenizer = load_model()
42
+
43
+ def validate_json(output: str) -> tuple:
44
+ """Validate and extract JSON from model output"""
45
+ try:
46
+ json_match = re.search(r'\{[\s\S]*\}', output)
47
+ if not json_match:
48
+ return False, None, "未找到JSON"
49
+
50
+ json_str = json_match.group(0)
51
+ json_str = re.sub(r',\s*\}', '}', json_str)
52
+ parsed = json.loads(json_str)
53
+ return True, parsed, "有效的JSON"
54
+ except json.JSONDecodeError:
55
+ return False, None, "無效的JSON格式"
56
+ except Exception:
57
+ return False, None, "解析JSON時出錯"
58
+
59
+ def extract_reservation_info(text: str):
60
+ """Extract reservation information from text"""
61
+ if model is None or tokenizer is None:
62
+ return {"error": "模型未加載成功,請刷新頁面重試"}, ""
63
+
64
+ try:
65
+ # Create chat template
66
+ messages = [
67
+ {"role": "system", "content": SYSTEM_PROMPT},
68
+ {"role": "user", "content": text}
69
+ ]
70
+
71
+ prompt = tokenizer.apply_chat_template(
72
+ messages,
73
+ tokenize=False,
74
+ add_generation_prompt=True
75
+ )
76
+
77
+ # Tokenize input
78
+ inputs = tokenizer(prompt, return_tensors="pt", padding=True)
79
+
80
+ # Generate response
81
+ with torch.no_grad():
82
+ outputs = model.generate(
83
+ **inputs,
84
+ max_new_tokens=64,
85
+ temperature=0.1,
86
+ pad_token_id=tokenizer.eos_token_id,
87
+ eos_token_id=tokenizer.eos_token_id,
88
+ do_sample=False,
89
+ )
90
+
91
+ # Extract assistant's response
92
+ prompt_length = len(inputs.input_ids[0])
93
+ assistant_output = tokenizer.decode(outputs[0][prompt_length:], skip_special_tokens=True)
94
+
95
+ # Validate and parse JSON
96
+ is_valid, parsed, message = validate_json(assistant_output)
97
+
98
+ if is_valid:
99
+ return parsed, assistant_output
100
+ else:
101
+ return {"error": message}, assistant_output
102
+
103
+ except Exception as e:
104
+ return {"error": f"處理時出錯: {str(e)}"}, ""
105
+
106
+ # Create Gradio v5 interface
107
+ def create_interface():
108
+ """Create the Gradio v5 interface"""
109
+ examples = [
110
+ "你好,我想訂明天晚上7點的位子,四位成人,電話是0912-345-678",
111
+ "週六下午三點,兩位,電話0987654321",
112
+ "預約下週三中午12點半,5人用餐,聯絡電話0912345678",
113
+ "我要訂位,3個人,今天下午6點"
114
+ ]
115
+
116
+ # Define custom CSS for better styling
117
+ custom_css = """
118
+ .gradio-container {
119
+ max-width: 1000px !important;
120
+ }
121
+ .container {
122
+ max-width: 1000px;
123
+ margin: 0 auto;
124
+ }
125
+ .header {
126
+ text-align: center;
127
+ padding: 20px;
128
+ background: linear-gradient(135deg, #FF4B4B 0%, #FF8E53 100%);
129
+ border-radius: 10px;
130
+ margin-bottom: 20px;
131
+ color: white;
132
+ }
133
+ .example-box {
134
+ background-color: #f5f5f5;
135
+ padding: 15px;
136
+ border-radius: 10px;
137
+ margin-bottom: 15px;
138
+ cursor: pointer;
139
+ }
140
+ .example-box:hover {
141
+ background-color: #e8e8e8;
142
+ }
143
+ """
144
+
145
+ with gr.Blocks(
146
+ title="🍽️ 餐廳訂位資訊提取器",
147
+ theme=gr.themes.Soft(),
148
+ css=custom_css
149
+ ) as demo:
150
+ # Header
151
+ gr.HTML("""
152
+ <div class="header">
153
+ <h1>🍽️ 餐廳訂位資訊提取器</h1>
154
+ <p>使用AI從中文訊息中自動提取訂位資訊</p>
155
+ </div>
156
+ """)
157
+
158
+ with gr.Row():
159
+ with gr.Column(scale=2):
160
+ # Input section
161
+ input_text = gr.Textbox(
162
+ label="輸入訂位訊息",
163
+ placeholder="例如: 你好,我想訂明天晚上7點的位子,四位成人,電話是0912-345-678",
164
+ lines=3,
165
+ max_lines=5
166
+ )
167
+
168
+ # Examples section
169
+ gr.Markdown("### 💡 示例訊息")
170
+ for i, example in enumerate(examples):
171
+ gr.HTML(f"""
172
+ <div class="example-box" onclick="document.getElementById('input-text').value = '{example}'; document.getElementById('input-text').dispatchEvent(new Event('input'));">
173
+ {example}
174
+ </div>
175
+ """)
176
+
177
+ submit_btn = gr.Button("提取資訊", variant="primary", size="lg")
178
+
179
+ with gr.Column(scale=3):
180
+ # Output section
181
+ gr.Markdown("### 📋 提取結果")
182
+
183
+ with gr.Tab("結構化結果"):
184
+ json_output = gr.JSON(label="提取結果")
185
+
186
+ with gr.Tab("原始輸出"):
187
+ raw_output = gr.Code(
188
+ label="模型原始輸出",
189
+ language="json",
190
+ interactive=False
191
+ )
192
+
193
+ # Stats section
194
+ with gr.Row():
195
+ with gr.Column():
196
+ people_count = gr.Number(label="👥 人數", interactive=False)
197
+ with gr.Column():
198
+ date_info = gr.Textbox(label="📅 預訂時間", interactive=False)
199
+ with gr.Column():
200
+ phone_info = gr.Textbox(label="📞 電話", interactive=False)
201
+
202
+ # Info panel
203
+ with gr.Accordion("ℹ️ 使用說明", open=False):
204
+ gr.Markdown("""
205
+ **支援提取的資訊:**
206
+ - 👥 人數 (num_people)
207
+ - 📅 預訂日期/時間 (reservation_date)
208
+ - 📞 電話號碼 (phone_num)
209
+
210
+ **注意事項:**
211
+ - 首次加載模型可能需要幾分鐘時間
212
+ - 如果遇到錯誤,請嘗試刷新頁面
213
+ - 模型會輸出JSON格式的結果
214
+ """)
215
+
216
+ # Footer
217
+ gr.Markdown("---")
218
+ gr.HTML("""
219
+ <div style="text-align: center; color: #666;">
220
+ <p>由 <a href="https://together.ai" target="_blank">Together AI</a> 提供技術支持 | 模型: Luigi/gemma-3-270m-it-dinercall-ner</p>
221
+ </div>
222
+ """)
223
+
224
+ # Hidden element for JavaScript interaction
225
+ gr.HTML("""
226
+ <input type="hidden" id="input-text">
227
+ <script>
228
+ document.addEventListener('DOMContentLoaded', function() {
229
+ const inputElement = document.querySelector('[aria-label="輸入訂位訊息"] textarea');
230
+ const hiddenInput = document.getElementById('input-text');
231
+
232
+ hiddenInput.addEventListener('input', function() {
233
+ inputElement.value = hiddenInput.value;
234
+ inputElement.dispatchEvent(new Event('input', { bubbles: true }));
235
+ });
236
+ });
237
+ </script>
238
+ """)
239
+
240
+ # Function to update stats
241
+ def update_stats(result):
242
+ """Update the statistics fields based on the result"""
243
+ if isinstance(result, dict) and "error" not in result:
244
+ return (
245
+ result.get("num_people", "未提供"),
246
+ result.get("reservation_date", "未提供"),
247
+ result.get("phone_num", "未提供")
248
+ )
249
+ return ("", "", "")
250
+
251
+ # Connect the function to the button
252
+ submit_btn.click(
253
+ fn=extract_reservation_info,
254
+ inputs=input_text,
255
+ outputs=[json_output, raw_output]
256
+ ).then(
257
+ fn=update_stats,
258
+ inputs=json_output,
259
+ outputs=[people_count, date_info, phone_info]
260
+ )
261
+
262
+ return demo
263
+
264
+ # Create and launch the interface
265
+ if __name__ == "__main__":
266
+ demo = create_interface()
267
+ demo.launch(
268
+ server_name="0.0.0.0",
269
+ server_port=7860,
270
+ share=False
271
+ )
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ gradio>=5.0.0
2
+ torch>=2.0.0
3
+ transformers>=4.30.0
4
+ accelerate>=0.20.0
5
+ sentencepiece>=0.1.99
6
+ protobuf>=3.20.0