ProfRod100 commited on
Commit
81ba821
Β·
verified Β·
1 Parent(s): 8578fe8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -232
app.py CHANGED
@@ -1,7 +1,7 @@
1
  import os
2
- import numpy as np
3
  import gradio as gr
4
  import joblib
 
5
 
6
  from sklearn.pipeline import Pipeline
7
  from sklearn.feature_extraction.text import TfidfVectorizer
@@ -10,284 +10,161 @@ from sklearn.linear_model import LogisticRegression
10
  from transformers import pipeline as hf_pipeline
11
 
12
 
13
- # ======================================================================
14
  # 1. Baseline de Sentimentos (TF-IDF + Logistic Regression)
15
- # ======================================================================
16
 
17
- BASELINE_PATH = os.getenv("MODEL_PATH", "baseline_pipe.pkl")
18
 
19
 
20
- def train_small_baseline(save_path: str = BASELINE_PATH,
21
- max_samples: int = 10000):
22
- """
23
- Treina um baseline pequeno usando uma amostra do dataset amazon_polarity.
24
- Usado apenas se baseline_pipe.pkl nao existir no Space.
25
- """
26
  from datasets import load_dataset
27
  import pandas as pd
28
 
29
  ds = load_dataset("amazon_polarity", split="train")
30
- ds_small = ds.shuffle(seed=42).select(range(min(max_samples, len(ds))))
31
 
32
- df = pd.DataFrame(
33
- {"text": ds_small["content"], "label": ds_small["label"]}
34
- )
35
 
36
- pipe = Pipeline(
37
- [
38
- ("tfidf", TfidfVectorizer(max_features=30000, ngram_range=(1, 2))),
39
- ("clf", LogisticRegression(max_iter=1000)),
40
- ]
41
- )
42
 
43
  pipe.fit(df["text"], df["label"])
44
  joblib.dump(pipe, save_path)
45
  return pipe
46
 
47
 
48
- def load_or_bootstrap_baseline():
49
- """
50
- Se existir baseline_pipe.pkl, carrega.
51
- Se nao existir e DISABLE_AUTOTRAIN != 1, treina um baseline pequeno.
52
- """
53
  if os.path.exists(BASELINE_PATH):
54
  return joblib.load(BASELINE_PATH)
55
-
56
- disable_auto = os.getenv("DISABLE_AUTOTRAIN", "0")
57
- if disable_auto == "1":
58
- return None
59
-
60
  return train_small_baseline()
61
 
62
 
63
- baseline_model = load_or_bootstrap_baseline()
64
 
65
 
66
- def classify_only(text: str):
67
- """
68
- Apenas classifica o sentimento (positivo/negativo) e retorna JSON.
69
- """
70
- if not text or text.strip() == "":
71
- return {"erro": "Digite um texto."}
72
-
73
- if baseline_model is None:
74
- return {
75
- "erro": (
76
- "Modelo baseline nao encontrado. "
77
- "Envie baseline_pipe.pkl na aba Files ou remova DISABLE_AUTOTRAIN."
78
- )
79
- }
80
 
81
  proba = baseline_model.predict_proba([text])[0]
82
- pred = int(np.argmax(proba))
83
- label = "positivo" if pred == 1 else "negativo"
84
  conf = float(np.max(proba))
85
-
86
  return {"sentimento": label, "confianca": round(conf, 3)}
87
 
88
 
89
- # ======================================================================
90
- # 2. IA Generativa (LLaMA 3) para resposta ao cliente
91
- # ======================================================================
92
-
93
- # ======================================================================
94
- # 2. IA Generativa (FLAN-T5) para resposta ao cliente
95
- # ======================================================================
96
-
97
- GEN_MODEL_ID = os.getenv("GEN_MODEL_ID", "google/flan-t5-base")
98
-
99
- # text2text-generation funciona muito bem com FLAN
100
- generator = hf_pipeline("text2text-generation", model=GEN_MODEL_ID)
101
-
102
-
103
- def build_prompt(history, user_text, sentimento_json):
104
- """
105
- Constroi um prompt amigavel para FLAN-T5, usando historico + sentimento.
106
- NENHUMA referencia a processo interno aparece na resposta.
107
- """
108
- sentimento = None
109
- confianca = None
110
- if isinstance(sentimento_json, dict):
111
- sentimento = sentimento_json.get("sentimento")
112
- confianca = sentimento_json.get("confianca")
113
-
114
- if sentimento is None:
115
- sentimento = "nao identificado"
116
-
117
- # CabeΓ§alho de instruΓ§Γ£o (modelo vΓͺ, cliente nΓ£o)
118
- prompt = (
119
- "VocΓͺ Γ© um atendente virtual educado, empΓ‘tico e profissional "
120
- "de uma loja online. Responda SEMPRE em portuguΓͺs do Brasil, "
121
- "usando entre 2 e 4 frases curtas, claras e naturais.\n\n"
122
- "InformaΓ§Γ£o de contexto (nΓ£o revele isso na resposta): "
123
- f"a ΓΊltima mensagem do cliente foi classificada com sentimento "
124
- f"'{sentimento}' (confianΓ§a {confianca}). "
125
- "Use isso apenas para ajustar o tom (mais empΓ‘tico se negativo, "
126
- "mais entusiasmado se positivo), mas nΓ£o mencione a palavra "
127
- "'sentimento', 'classificaΓ§Γ£o' ou 'modelo'.\n\n"
128
- "HistΓ³rico da conversa:\n"
 
 
129
  )
130
 
131
- # HistΓ³rico anterior
132
- if history:
133
- for user, bot in history:
134
- prompt += f"Cliente: {user}\n"
135
- prompt += f"Atendente: {bot}\n"
136
-
137
- # Nova mensagem
138
- prompt += f"Cliente: {user_text}\n"
139
- prompt += "Atendente:"
140
- return prompt
141
-
142
-
143
- def generate_reply_with_history(history, user_text, sentimento_json):
144
- """
145
- Gera uma resposta levando em conta historico + sentimento,
146
- usando FLAN-T5 em modo text2text-generation.
147
- """
148
- if not user_text or user_text.strip() == "":
149
- return "Digite uma mensagem."
150
-
151
- prompt = build_prompt(history, user_text, sentimento_json)
152
-
153
- outputs = generator(
154
- prompt,
155
- max_length=160,
156
- do_sample=True,
157
- temperature=0.7,
158
- top_p=0.9,
159
- )
160
 
161
- reply = outputs[0]["generated_text"]
162
- return reply.strip()
163
 
 
164
 
165
- # ======================================================================
166
- # 3. FunΓ§Γ£o de passo do Chatbot (para o Gradio)
167
- # ======================================================================
168
 
169
- def chatbot_step(history, user_text):
170
- """
171
- - Analisa sentimento da nova mensagem
172
- - Gera resposta com LLaMA 3
173
- - Atualiza historico
174
- """
175
- if not user_text or user_text.strip() == "":
176
- return history, "", {}, history
177
 
178
- sentiment = classify_only(user_text)
179
- reply = generate_reply_with_history(history, user_text, sentiment)
180
 
181
- if history is None:
182
- history = []
183
 
184
- history = history + [(user_text, reply)]
 
 
185
 
186
- return history, "", sentiment, history
 
187
 
188
 
189
- # ======================================================================
190
- # 4. Interface Gradio - abas, design e historico
191
- # ======================================================================
192
 
193
- with gr.Blocks(
194
- title="Chatbot de Sentimentos - Professor Rodrigo",
195
- theme=gr.themes.Default().set(
196
- border_radius="8px",
197
- shadow_drop="small",
198
- font=["Inter", "system-ui", "sans-serif"],
199
- ),
200
- css="""
201
- #header-markdown h1 { font-size: 1.8rem; }
202
- #header-markdown p { font-size: 0.95rem; }
203
- """
204
- ) as demo:
205
  gr.Markdown(
206
  """
207
- <div id="header-markdown">
208
-
209
  # Chatbot de Sentimentos (ML + IA Generativa)
 
210
 
211
- **Professor Rodrigo** β€” Projeto Final de Machine Learning & Deep Learning
 
 
 
 
 
212
 
213
- - ClassificaΓ§Γ£o: TF-IDF + RegressΓ£o LogΓ­stica (baseline).
214
- - GeraΓ§Γ£o: modelo `LLaMA 3` (Instruct) para respostas em PT-BR.
 
 
 
215
 
216
- > Dica didΓ‘tica: envie **`baseline_pipe.pkl`** na aba *Files* do Space
217
- > para usar um modelo de sentimentos treinado pelo seu grupo.
 
 
218
 
219
- </div>
220
- """,
221
- elem_id="header-markdown",
222
- )
223
 
224
- with gr.Tab("AnΓ‘lise de Sentimento (isolada)"):
225
- with gr.Row():
226
- with gr.Column(scale=3):
227
- input_text = gr.Textbox(
228
- label="Digite uma avaliaΓ§Γ£o de produto",
229
- lines=5,
230
- placeholder=(
231
- "Ex.: O produto chegou rΓ‘pido e superou minhas expectativas "
232
- "ou: O produto chegou quebrado, estou muito chateado."
233
- ),
234
- )
235
- btn_analisar = gr.Button("Analisar sentimento", variant="primary")
236
- with gr.Column(scale=2):
237
- output_json = gr.JSON(
238
- label="Resultado da classificaΓ§Γ£o (baseline)",
239
- )
240
-
241
- btn_analisar.click(classify_only, inputs=input_text, outputs=output_json)
242
-
243
- with gr.Tab("Chatbot (AnΓ‘lise + Resposta com histΓ³rico)"):
244
- with gr.Row():
245
- with gr.Column(scale=3):
246
- chat_history = gr.Chatbot(
247
- label="Conversa com o atendente virtual",
248
- height=400,
249
- )
250
- user_input = gr.Textbox(
251
- label="Mensagem do cliente",
252
- lines=4,
253
- placeholder="Ex.: Estou chateado, o produto Γ© ruim.",
254
- )
255
- with gr.Row():
256
- send_btn = gr.Button("Enviar", variant="primary")
257
- clear_btn = gr.Button("Limpar conversa")
258
-
259
- with gr.Column(scale=2):
260
- last_sentiment = gr.JSON(
261
- label="Sentimento da ΓΊltima mensagem",
262
- )
263
- gr.Markdown(
264
- """
265
- **Como funciona esta aba?**
266
-
267
- 1. O cliente envia uma mensagem.
268
- 2. O baseline classifica o sentimento (positivo/negativo).
269
- 3. O modelo LLaMA 3 gera uma resposta empΓ‘tica, usando o sentimento apenas como contexto.
270
- 4. O histΓ³rico da conversa Γ© mantido e influencia as respostas seguintes.
271
- """
272
- )
273
-
274
- state_history = gr.State([])
275
-
276
- send_btn.click(
277
- chatbot_step,
278
- inputs=[state_history, user_input],
279
- outputs=[chat_history, user_input, last_sentiment, state_history],
280
- )
281
-
282
- def clear_chat():
283
- return [], {}, []
284
-
285
- clear_btn.click(
286
- clear_chat,
287
- inputs=None,
288
- outputs=[chat_history, last_sentiment, state_history],
289
- )
290
-
291
-
292
- if __name__ == "__main__":
293
- demo.launch()
 
1
  import os
 
2
  import gradio as gr
3
  import joblib
4
+ import numpy as np
5
 
6
  from sklearn.pipeline import Pipeline
7
  from sklearn.feature_extraction.text import TfidfVectorizer
 
10
  from transformers import pipeline as hf_pipeline
11
 
12
 
13
+ # ============================================================
14
  # 1. Baseline de Sentimentos (TF-IDF + Logistic Regression)
15
+ # ============================================================
16
 
17
+ BASELINE_PATH = "baseline_pipe.pkl"
18
 
19
 
20
+ def train_small_baseline(save_path=BASELINE_PATH, max_samples=8000):
21
+ """Treina um baseline pequeno (caso o aluno nΓ£o envie o .pkl)."""
 
 
 
 
22
  from datasets import load_dataset
23
  import pandas as pd
24
 
25
  ds = load_dataset("amazon_polarity", split="train")
26
+ ds_small = ds.shuffle(seed=42).select(range(max_samples))
27
 
28
+ df = pd.DataFrame({"text": ds_small["content"], "label": ds_small["label"]})
 
 
29
 
30
+ pipe = Pipeline([
31
+ ("tfidf", TfidfVectorizer(max_features=25000, ngram_range=(1, 2))),
32
+ ("clf", LogisticRegression(max_iter=1200)),
33
+ ])
 
 
34
 
35
  pipe.fit(df["text"], df["label"])
36
  joblib.dump(pipe, save_path)
37
  return pipe
38
 
39
 
40
+ def load_baseline():
 
 
 
 
41
  if os.path.exists(BASELINE_PATH):
42
  return joblib.load(BASELINE_PATH)
 
 
 
 
 
43
  return train_small_baseline()
44
 
45
 
46
+ baseline_model = load_baseline()
47
 
48
 
49
+ def classify_sentiment(text):
50
+ if not text.strip():
51
+ return {"erro": "Digite algo para analisar."}
 
 
 
 
 
 
 
 
 
 
 
52
 
53
  proba = baseline_model.predict_proba([text])[0]
54
+ label = "positivo" if np.argmax(proba) == 1 else "negativo"
 
55
  conf = float(np.max(proba))
 
56
  return {"sentimento": label, "confianca": round(conf, 3)}
57
 
58
 
59
+ # ============================================================
60
+ # 2. IA Generativa β€” LLaMA-3-8B-Instruct
61
+ # ============================================================
62
+
63
+ GEN_MODEL = "meta-llama/Meta-Llama-3-8B-Instruct"
64
+
65
+ generator = hf_pipeline(
66
+ "text-generation",
67
+ model=GEN_MODEL,
68
+ max_new_tokens=180,
69
+ temperature=0.5,
70
+ top_p=0.9
71
+ )
72
+
73
+
74
+ SYSTEM_PROMPT = """
75
+ VocΓͺ Γ© um atendente virtual profissional, educado e empΓ‘tico.
76
+ Responda sempre em portuguΓͺs do Brasil, de forma natural
77
+ e sem mencionar que Γ© uma IA. Nunca justifique o processo interno.
78
+ """
79
+
80
+
81
+ def build_final_prompt(history, user_msg, sentimento_json):
82
+ sentimento = sentimento_json.get("sentimento", "neutro")
83
+
84
+ if sentimento == "negativo":
85
+ intent = """
86
+ O cliente estΓ‘ insatisfeito. Mostre empatia, peΓ§a desculpas,
87
+ demonstre interesse em resolver e peΓ§a informaΓ§Γ΅es adicionais.
88
+ """
89
+ elif sentimento == "positivo":
90
+ intent = """
91
+ O cliente estΓ‘ satisfeito. AgradeΓ§a com entusiasmo,
92
+ reforce os pontos positivos e demonstre proximidade.
93
+ """
94
+ else:
95
+ intent = """
96
+ Sentimento indefinido. Responda de forma neutra, cordial e prestativa.
97
+ """
98
+
99
+ history_text = "\n".join(
100
+ [f"Cliente: {msg[0]}\nAtendente: {msg[1]}" for msg in history]
101
  )
102
 
103
+ final_prompt = f"""
104
+ {SYSTEM_PROMPT}
105
+
106
+ Contexto da conversa:
107
+ {history_text}
108
+
109
+ InstruΓ§Γ΅es:
110
+ {intent}
111
+
112
+ Mensagem do cliente:
113
+ "{user_msg}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
+ Gere uma resposta natural, com 2 a 4 frases.
116
+ """
117
 
118
+ return final_prompt
119
 
 
 
 
120
 
121
+ def chat_generate(history, user_input):
122
+ if not user_input.strip():
123
+ return history, "Digite uma mensagem."
 
 
 
 
 
124
 
125
+ sentimento = classify_sentiment(user_input)
126
+ final_prompt = build_final_prompt(history, user_input, sentimento)
127
 
128
+ result = generator(final_prompt)[0]["generated_text"]
 
129
 
130
+ # Extrair somente a resposta final (remove prompt repetido)
131
+ if final_prompt in result:
132
+ result = result.split(final_prompt)[-1].strip()
133
 
134
+ history.append((user_input, result))
135
+ return history, result
136
 
137
 
138
+ # ============================================================
139
+ # 3. Interface Gradio (compatΓ­vel com HF antigo)
140
+ # ============================================================
141
 
142
+ with gr.Blocks(title="Chatbot de Sentimentos - Prof. Rodrigo", theme=gr.themes.Default()) as demo:
 
 
 
 
 
 
 
 
 
 
 
143
  gr.Markdown(
144
  """
 
 
145
  # Chatbot de Sentimentos (ML + IA Generativa)
146
+ **Professor Rodrigo β€” Projeto Final ML & DL**
147
 
148
+ - ClassificaΓ§Γ£o de sentimento com TF-IDF + RegressΓ£o LogΓ­stica
149
+ - Respostas naturais geradas por LLaMA-3-8B-Instruct
150
+ - Suporte a histΓ³rico de conversa
151
+ - Envie `baseline_pipe.pkl` na aba **Files** caso tenha treinado seu prΓ³prio modelo
152
+ """
153
+ )
154
 
155
+ with gr.Tab("AnΓ‘lise de Sentimento"):
156
+ text_in = gr.Textbox(label="Digite um comentΓ‘rio", lines=4)
157
+ text_out = gr.JSON(label="Resultado")
158
+ btn_analisar = gr.Button("Analisar sentimento")
159
+ btn_analisar.click(classify_sentiment, inputs=text_in, outputs=text_out)
160
 
161
+ with gr.Tab("Chatbot (ConversaΓ§Γ£o + Resposta)"):
162
+ chatbot = gr.Chatbot(label="HistΓ³rico de conversa")
163
+ user_box = gr.Textbox(label="Mensagem do cliente", lines=3)
164
+ send_btn = gr.Button("Enviar e gerar resposta")
165
 
166
+ send_btn.click(chat_generate,
167
+ inputs=[chatbot, user_box],
168
+ outputs=[chatbot, user_box])
 
169
 
170
+ demo.launch()