MrSimple01 commited on
Commit
d0ac18d
·
verified ·
1 Parent(s): 01c7da5

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +446 -0
app.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import argparse
3
+ import warnings
4
+ import time
5
+ from typing import Dict, Tuple, List, Optional
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+ from tqdm.auto import tqdm
12
+ import google.generativeai as genai
13
+ from tenacity import retry, stop_after_attempt, wait_exponential
14
+ from sentence_transformers import SentenceTransformer
15
+ from sklearn.metrics.pairwise import cosine_similarity
16
+ import gradio as gr
17
+
18
+ # Suppress warnings
19
+ warnings.filterwarnings("ignore")
20
+
21
+ @dataclass
22
+ class EvaluationConfig:
23
+ api_key: str
24
+ model_name: str = "gemini-1.5-flash"
25
+ batch_size: int = 5
26
+ retry_attempts: int = 5
27
+ min_wait: int = 4
28
+ max_wait: int = 60
29
+ score_scale: Tuple[int, int] = (0, 100)
30
+
31
+ class EvaluationPrompts:
32
+ @staticmethod
33
+ def get_first_check(original_prompt: str, response: str) -> str:
34
+ return f"""Оцените следующий ответ по шкале от 0 до 100:
35
+ Оригинальный запрос: {original_prompt}
36
+ Ответ: {response}
37
+ Оцените по критериям:
38
+ 1. Креативность (уникальность и оригинальность ответа)
39
+ 2. Разнообразие (использование разных языковых средств)
40
+ 3. Релевантность (соответствие запросу)
41
+ Дайте только числовые оценки в формате:
42
+ Креативность: [число]
43
+ Разнообразие: [число]
44
+ Релевантность: [число]"""
45
+
46
+ @staticmethod
47
+ def get_second_check(original_prompt: str, response: str) -> str:
48
+ return f"""Вы — эксперт по оценке качества текстов, обладающий глубокими знаниями в области лингвистики, креативного письма и искусственного интеллекта. Ваша задача — объективно оценить представленный ответ по следующим критериям.
49
+
50
+ ### **Оригинальный запрос:**
51
+ {original_prompt}
52
+
53
+ ### **Ответ:**
54
+ {response}
55
+
56
+ ## **Инструкция по оценке**
57
+ Оцените ответ по шкале от 0 до 100 по трем критериям:
58
+
59
+ 1. **Креативность** – Насколько ответ уникален и оригинален? Есть ли неожиданные, но уместные идеи?
60
+ 2. **Разнообразие** – Использует ли ответ различные стилистические приемы, примеры, аналогии, синонимы? Насколько он выразителен?
61
+ 3. **Релевантность** – Насколько ответ соответствует запросу? Полностью ли он отвечает на поставленный вопрос?
62
+
63
+ ### **Формат ответа:**
64
+ Выведите оценки в точном формате:
65
+ Креативность: [число]
66
+ Разнообразие: [число]
67
+ Релевантность: [число]
68
+
69
+ Затем подробно объясните каждую оценку, используя примеры из ответа. Если какая-то оценка ниже 50, дайте конкретные рекомендации по улучшению."""
70
+
71
+ @staticmethod
72
+ def get_third_check(original_prompt: str, response: str) -> str:
73
+ return f"""Вы — эксперт по анализу текстов. Ваша задача — оценить ответ на запрос по шкале от 0 до 100 по трем критериям.
74
+
75
+ ### **Оригинальный запрос:**
76
+ {original_prompt}
77
+
78
+ ### **Ответ:**
79
+ {response}
80
+
81
+ ## **Критерии оценки:**
82
+ 1. **Креативность** – Насколько ответ уникален и оригинален? Используются ли необычные идеи и неожиданные подходы?
83
+ 2. **Разнообразие** – Применяются ли разные языковые конструкции, примеры, аналогии, синонимы?
84
+ 3. **Релевантность** – Насколько ответ соответствует запросу? Полностью ли он отвечает на поставленный вопрос?
85
+
86
+ Выведите оценки в точном формате:
87
+ Креативность: [число]
88
+ Разнообразие: [число]
89
+ Релевантность: [число]"""
90
+
91
+
92
+ class ResponseEvaluator:
93
+ def __init__(self, config: EvaluationConfig):
94
+ """Initialize the evaluator with given configuration"""
95
+ self.config = config
96
+ self.model = self._setup_model()
97
+
98
+ def _setup_model(self) -> genai.GenerativeModel:
99
+ """Set up the Gemini model"""
100
+ genai.configure(api_key=self.config.api_key)
101
+ return genai.GenerativeModel(self.config.model_name)
102
+
103
+ @retry(
104
+ stop=stop_after_attempt(5),
105
+ wait=wait_exponential(multiplier=1, min=4, max=60)
106
+ )
107
+ def evaluate_single_response(self, original_prompt: str, response: str) -> Tuple[Dict[str, float], str]:
108
+ """Evaluate a single response using the configured model"""
109
+ evaluation_prompts = self._create_evaluation_prompt(original_prompt, response)
110
+ all_scores = []
111
+ all_texts = []
112
+
113
+ for prompt in evaluation_prompts:
114
+ try:
115
+ evaluation = self.model.generate_content(prompt)
116
+ scores = self._parse_evaluation_scores(evaluation.text)
117
+ all_scores.append(scores)
118
+ all_texts.append(evaluation.text)
119
+ except Exception as e:
120
+ print(f"Error with prompt: {str(e)}")
121
+ all_scores.append({
122
+ "Креативность": 0,
123
+ "Разнообразие": 0,
124
+ "Релевантность": 0,
125
+ "Среднее": 0
126
+ })
127
+ all_texts.append("Error in evaluation")
128
+
129
+ final_scores = {
130
+ "Креативность": np.mean([s.get("Креативность", 0) for s in all_scores]),
131
+ "Разнообразие": np.mean([s.get("Разнообразие", 0) for s in all_scores]),
132
+ "Релевантность": np.mean([s.get("Релевантность", 0) for s in all_scores])
133
+ }
134
+ final_scores["Среднее"] = np.mean(list(final_scores.values()))
135
+
136
+ return final_scores, "\n\n".join(all_texts)
137
+
138
+ def _create_evaluation_prompt(self, original_prompt: str, response: str) -> List[str]:
139
+ """Create multiple evaluation prompts"""
140
+ prompts = []
141
+ prompts.append(EvaluationPrompts.get_first_check(original_prompt, response))
142
+ prompts.append(EvaluationPrompts.get_second_check(original_prompt, response))
143
+ prompts.append(EvaluationPrompts.get_third_check(original_prompt, response))
144
+ return prompts
145
+
146
+ def _parse_evaluation_scores(self, evaluation_text: str) -> Dict[str, float]:
147
+ """Parse evaluation text into scores dictionary"""
148
+ scores = {}
149
+ for line in evaluation_text.strip().split('\n'):
150
+ if ':' in line:
151
+ parts = line.split(':')
152
+ if len(parts) >= 2:
153
+ metric, score_text = parts[0], ':'.join(parts[1:])
154
+ try:
155
+ score_text = score_text.strip()
156
+ score = float(''.join(c for c in score_text if c.isdigit() or c == '.'))
157
+ scores[metric.strip()] = score
158
+ except ValueError:
159
+ continue
160
+
161
+ if scores:
162
+ scores['Среднее'] = np.mean([v for k, v in scores.items() if k != 'Среднее'])
163
+
164
+ return scores
165
+
166
+ def evaluate_dataset(self, df: pd.DataFrame, prompt_col: str, answer_col: str) -> pd.DataFrame:
167
+ """Evaluate all responses in the dataset"""
168
+ evaluations = []
169
+ eval_answers = []
170
+
171
+ total_batches = (len(df) + self.config.batch_size - 1) // self.config.batch_size
172
+
173
+ for i in range(0, len(df), self.config.batch_size):
174
+ batch = df.iloc[i:i+self.config.batch_size]
175
+
176
+ with tqdm(batch.iterrows(), total=len(batch),
177
+ desc=f"Batch {i//self.config.batch_size + 1}/{total_batches}") as pbar:
178
+ for _, row in pbar:
179
+ try:
180
+ scores, eval_text = self.evaluate_single_response(
181
+ str(row[prompt_col]),
182
+ str(row[answer_col])
183
+ )
184
+ evaluations.append(scores)
185
+ eval_answers.append(eval_text)
186
+ except Exception as e:
187
+ print(f"Error processing row {_}: {str(e)}")
188
+ evaluations.append({
189
+ "Креативность": 0,
190
+ "Разнообразие": 0,
191
+ "Релевантность": 0,
192
+ "Среднее": 0
193
+ })
194
+ eval_answers.append("Error in evaluation")
195
+
196
+ time.sleep(2)
197
+
198
+ time.sleep(10)
199
+
200
+ return self._create_evaluation_dataframe(df, evaluations, eval_answers)
201
+
202
+ def _create_evaluation_dataframe(self,
203
+ original_df: pd.DataFrame,
204
+ evaluations: List[Dict],
205
+ eval_answers: List[str]) -> pd.DataFrame:
206
+ score_df = pd.DataFrame(evaluations)
207
+ df = original_df.copy()
208
+ df['gemini_eval_answer'] = eval_answers
209
+ return pd.concat([df, score_df], axis=1)
210
+
211
+
212
+ class StabilityEvaluator:
213
+ def __init__(self, model_name='paraphrase-MiniLM-L6-v2'):
214
+ self.model = SentenceTransformer(model_name)
215
+
216
+ def calculate_similarity(self, prompts, outputs):
217
+ prompt_embeddings = self.model.encode(prompts)
218
+ output_embeddings = self.model.encode(outputs)
219
+
220
+ similarities = cosine_similarity(prompt_embeddings, output_embeddings)
221
+
222
+ stability_coefficients = np.diag(similarities)
223
+
224
+ return {
225
+ 'stability_score': np.mean(stability_coefficients) * 100, # Scale to 0-100
226
+ 'stability_std': np.std(stability_coefficients) * 100,
227
+ 'individual_similarities': stability_coefficients
228
+ }
229
+
230
+ def evaluate_dataset(self, df, prompt_col='rus_prompt'):
231
+ """Evaluate stability for multiple answer columns"""
232
+ results = {}
233
+
234
+ # Find columns ending with '_answers'
235
+ answer_columns = [col for col in df.columns if col.endswith('_answers')]
236
+
237
+ for column in answer_columns:
238
+ model_name = column.replace('_answers', '')
239
+ results[model_name] = self.calculate_similarity(
240
+ df[prompt_col].tolist(),
241
+ df[column].tolist()
242
+ )
243
+
244
+ return results
245
+
246
+
247
+ class BenchmarkEvaluator:
248
+ def __init__(self, gemini_api_key):
249
+ """Initialize both evaluators"""
250
+ self.creative_evaluator = ResponseEvaluator(
251
+ EvaluationConfig(api_key=gemini_api_key)
252
+ )
253
+ self.stability_evaluator = StabilityEvaluator()
254
+
255
+ def evaluate_model(self, df, model_name, prompt_col='rus_prompt'):
256
+ """Evaluate a single model's responses"""
257
+ answer_col = f"{model_name}_answers"
258
+
259
+ if answer_col not in df.columns:
260
+ raise ValueError(f"Column {answer_col} not found in dataframe")
261
+
262
+ print(f"Evaluating creativity for {model_name}...")
263
+ creative_df = self.creative_evaluator.evaluate_dataset(df, prompt_col, answer_col)
264
+
265
+ print(f"Evaluating stability for {model_name}...")
266
+ stability_results = self.stability_evaluator.calculate_similarity(
267
+ df[prompt_col].tolist(),
268
+ df[answer_col].tolist()
269
+ )
270
+
271
+ creative_score = creative_df["Среднее"].mean()
272
+ stability_score = stability_results['stability_score']
273
+ combined_score = (creative_score + stability_score) / 2
274
+
275
+ results = {
276
+ 'model': model_name,
277
+ 'creativity_score': creative_score,
278
+ 'stability_score': stability_score,
279
+ 'combined_score': combined_score,
280
+ 'creative_details': {
281
+ 'creativity': creative_df["Креативность"].mean(),
282
+ 'diversity': creative_df["Разнообразие"].mean(),
283
+ 'relevance': creative_df["Релевантность"].mean(),
284
+ },
285
+ 'stability_details': stability_results
286
+ }
287
+
288
+ # Save detailed results
289
+ output_file = f'evaluated_responses_{model_name}.csv'
290
+ creative_df.to_csv(output_file, index=False)
291
+ print(f"Detailed results saved to {output_file}")
292
+
293
+ return results
294
+
295
+ def evaluate_all_models(self, df, models=None, prompt_col='rus_prompt'):
296
+ """Evaluate multiple models from the dataframe"""
297
+ if models is None:
298
+ # Find all columns ending with _answers
299
+ answer_cols = [col for col in df.columns if col.endswith('_answers')]
300
+ models = [col.replace('_answers', '') for col in answer_cols]
301
+
302
+ results = []
303
+ for model in models:
304
+ try:
305
+ model_results = self.evaluate_model(df, model, prompt_col)
306
+ results.append(model_results)
307
+ print(f"Completed evaluation for {model}")
308
+ except Exception as e:
309
+ print(f"Error evaluating {model}: {str(e)}")
310
+
311
+ benchmark_df = pd.DataFrame(results)
312
+ benchmark_df.to_csv('benchmark_results.csv', index=False)
313
+ print("Benchmark completed. Results saved to benchmark_results.csv")
314
+
315
+ return benchmark_df
316
+
317
+
318
+ def evaluate_single_response(gemini_api_key, prompt, response, model_name="Test Model"):
319
+ """Evaluate a single response for the UI"""
320
+ # Create a temporary dataframe
321
+ df = pd.DataFrame({
322
+ 'rus_prompt': [prompt],
323
+ f'{model_name}_answers': [response]
324
+ })
325
+
326
+ evaluator = BenchmarkEvaluator(gemini_api_key)
327
+
328
+ try:
329
+ result = evaluator.evaluate_model(df, model_name)
330
+
331
+ # Format the result for displaying in UI
332
+ output = {
333
+ 'Creativity Score': f"{result['creative_details']['creativity']:.2f}",
334
+ 'Diversity Score': f"{result['creative_details']['diversity']:.2f}",
335
+ 'Relevance Score': f"{result['creative_details']['relevance']:.2f}",
336
+ 'Average Creative Score': f"{result['creativity_score']:.2f}",
337
+ 'Stability Score': f"{result['stability_score']:.2f}",
338
+ 'Combined Score': f"{result['combined_score']:.2f}"
339
+ }
340
+
341
+ return output
342
+ except Exception as e:
343
+ return {
344
+ 'Error': str(e)
345
+ }
346
+
347
+
348
+ def create_gradio_interface():
349
+ """Create Gradio interface for evaluation app"""
350
+ with gr.Blocks(title="Model Response Evaluator") as app:
351
+ gr.Markdown("# Model Response Evaluator")
352
+ gr.Markdown("Evaluate model responses for creativity, diversity, relevance, and stability.")
353
+
354
+ with gr.Tab("Single Response Evaluation"):
355
+ with gr.Row():
356
+ gemini_api_key = gr.Textbox(label="Gemini API Key", type="password")
357
+
358
+ with gr.Row():
359
+ with gr.Column():
360
+ prompt = gr.Textbox(label="Original Prompt", lines=3)
361
+ response = gr.Textbox(label="Model Response", lines=6)
362
+ model_name = gr.Textbox(label="Model Name", value="Test Model")
363
+
364
+ evaluate_btn = gr.Button("Evaluate Response")
365
+
366
+ with gr.Column():
367
+ output = gr.JSON(label="Evaluation Results")
368
+
369
+ evaluate_btn.click(
370
+ evaluate_single_response,
371
+ inputs=[gemini_api_key, prompt, response, model_name],
372
+ outputs=output
373
+ )
374
+
375
+ with gr.Tab("Batch Evaluation"):
376
+ with gr.Row():
377
+ gemini_api_key_batch = gr.Textbox(label="Gemini API Key", type="password")
378
+
379
+ with gr.Row():
380
+ csv_file = gr.File(label="Upload CSV with responses")
381
+ prompt_col = gr.Textbox(label="Prompt Column Name", value="rus_prompt")
382
+ models_input = gr.Textbox(label="Model names (comma-separated, leave blank for auto-detection)")
383
+
384
+ evaluate_batch_btn = gr.Button("Run Benchmark")
385
+ benchmark_output = gr.DataFrame(label="Benchmark Results")
386
+
387
+ def evaluate_batch(api_key, file, prompt_column, models_text):
388
+ try:
389
+ # Load the CSV file
390
+ file_path = file.name
391
+ df = pd.read_csv(file_path)
392
+
393
+ # Process model names if provided
394
+ models = None
395
+ if models_text.strip():
396
+ models = [m.strip() for m in models_text.split(',')]
397
+
398
+ # Run the evaluation
399
+ evaluator = BenchmarkEvaluator(api_key)
400
+ results = evaluator.evaluate_all_models(df, models, prompt_column)
401
+
402
+ return results
403
+ except Exception as e:
404
+ return pd.DataFrame({'Error': [str(e)]})
405
+
406
+ evaluate_batch_btn.click(
407
+ evaluate_batch,
408
+ inputs=[gemini_api_key_batch, csv_file, prompt_col, models_input],
409
+ outputs=benchmark_output
410
+ )
411
+
412
+ return app
413
+
414
+
415
+ def main():
416
+ parser = argparse.ArgumentParser(description="Model Response Evaluator")
417
+ parser.add_argument("--gemini_api_key", type=str, help="Gemini API Key", default=os.environ.get("GEMINI_API_KEY"))
418
+ parser.add_argument("--input_file", type=str, help="Input CSV file with model responses")
419
+ parser.add_argument("--models", type=str, help="Comma-separated list of model names to evaluate")
420
+ parser.add_argument("--prompt_col", type=str, default="rus_prompt", help="Column name containing prompts")
421
+ parser.add_argument("--web", action="store_true", help="Launch web interface")
422
+
423
+ args = parser.parse_args()
424
+
425
+ if args.web:
426
+ app = create_gradio_interface()
427
+ app.launch(share=True)
428
+ elif args.input_file:
429
+ if not args.gemini_api_key:
430
+ print("Error: Gemini API key is required. Set GEMINI_API_KEY environment variable or pass --gemini_api_key")
431
+ return
432
+
433
+ df = pd.read_csv(args.input_file)
434
+ models = None
435
+ if args.models:
436
+ models = [m.strip() for m in args.models.split(',')]
437
+
438
+ evaluator = BenchmarkEvaluator(args.gemini_api_key)
439
+ evaluator.evaluate_all_models(df, models, args.prompt_col)
440
+ else:
441
+ print("Error: Either --input_file or --web argument is required")
442
+ print("Run with --help for usage information")
443
+
444
+
445
+ if __name__ == "__main__":
446
+ main()