Commit
f2d7045
Β·
verified Β·
1 Parent(s): cfc7bc4

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +363 -0
  2. requirements.txt +3 -0
app.py ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import gradio as gr
4
+ import json
5
+ import aisuite as ai
6
+ import os
7
+ from dotenv import load_dotenv
8
+ load_dotenv()
9
+
10
+ # Setup client
11
+ client = ai.Client()
12
+
13
+ def generate_draft(topic: str, model: str = "openai:gpt-4o") -> str:
14
+ prompt = f"""You are an expert essay writer with strong analytical skills.
15
+
16
+ TASK: Write a compelling argumentative essay on the following topic:
17
+ "{topic}"
18
+
19
+ REQUIREMENTS:
20
+ 1. Structure: Introduction with clear thesis β†’ 3 body paragraphs β†’ Conclusion
21
+ 2. Each body paragraph should have: claim, evidence/reasoning, and connection to thesis
22
+ 3. Address at least one counterargument and refute it
23
+ 4. Use clear transitions between paragraphs
24
+ 5. Aim for 500 words
25
+
26
+ Write the complete essay now:"""
27
+
28
+ response = client.chat.completions.create(
29
+ model=model,
30
+ messages=[{"role": "user", "content": prompt}],
31
+ temperature=1.0,
32
+ )
33
+ return response.choices[0].message.content
34
+
35
+
36
+ def reflect_on_draft(draft: str, model: str = "openai:o4-mini") -> str:
37
+ prompt = f"""You are a rigorous writing instructor providing constructive feedback.
38
+
39
+ ESSAY TO REVIEW:
40
+ \"\"\"
41
+ {draft}
42
+ \"\"\"
43
+
44
+ Analyze this essay across these dimensions and provide specific, actionable feedback:
45
+
46
+ 1. **THESIS CLARITY**: Is the main argument clear and specific?
47
+ 2. **ARGUMENT STRENGTH**: Are claims well-supported? Is reasoning logical?
48
+ 3. **EVIDENCE QUALITY**: Are examples concrete and relevant?
49
+ 4. **STRUCTURE**: Does the organization flow logically?
50
+ 5. **COUNTERARGUMENTS**: Are opposing views addressed fairly?
51
+ 6. **STYLE & CLARITY**: Is the writing concise and clear?
52
+
53
+ For each dimension, identify what works well and what needs improvement.
54
+ End with your TOP 3 PRIORITY improvements."""
55
+
56
+ response = client.chat.completions.create(
57
+ model=model,
58
+ messages=[{"role": "user", "content": prompt}],
59
+ temperature=1.0,
60
+ )
61
+ return response.choices[0].message.content
62
+
63
+
64
+ def revise_draft(original_draft: str, reflection: str, model: str = "openai:gpt-4o") -> str:
65
+ prompt = f"""You are an expert editor tasked with improving an essay based on feedback.
66
+
67
+ ORIGINAL ESSAY:
68
+ \"\"\"
69
+ {original_draft}
70
+ \"\"\"
71
+
72
+ FEEDBACK RECEIVED:
73
+ \"\"\"
74
+ {reflection}
75
+ \"\"\"
76
+
77
+ REVISION INSTRUCTIONS:
78
+ 1. Address EACH piece of feedback
79
+ 2. Strengthen the thesis if unclear
80
+ 3. Add concrete evidence where suggested
81
+ 4. Improve transitions between paragraphs
82
+ 5. Ensure counterarguments are properly addressed
83
+ 6. Fix any awkward phrasing
84
+
85
+ IMPORTANT:
86
+ - Return ONLY the complete revised essay
87
+ - The revised essay MUST be at least 400 words
88
+ - If the original is unclear, expand it into a full essay"""
89
+
90
+ response = client.chat.completions.create(
91
+ model=model,
92
+ messages=[{"role": "user", "content": prompt}],
93
+ temperature=1.0,
94
+ )
95
+ return response.choices[0].message.content
96
+
97
+
98
+ # ============================================================
99
+ # EVALUATION SYSTEM
100
+ # ============================================================
101
+
102
+ EVALUATION_CRITERIA = {
103
+ "thesis_clear": "The thesis is stated in ONE clear sentence",
104
+ "thesis_debatable": "The thesis makes a debatable claim",
105
+ "thesis_specific": "The thesis is narrow and focused",
106
+ "thesis_positioned": "The thesis appears in the introduction",
107
+ "multiple_reasons": "At least 3 DISTINCT supporting reasons",
108
+ "reasons_developed": "Each reason is explained in depth",
109
+ "logical_progression": "Arguments build on each other",
110
+ "no_logical_fallacies": "No logical fallacies present",
111
+ "concrete_examples": "Specific real-world examples included",
112
+ "named_sources": "References specific studies/stats/experts by name",
113
+ "evidence_explained": "Evidence is analyzed, not just dropped in",
114
+ "varied_evidence": "Multiple types of evidence used",
115
+ "counter_acknowledged": "At least one opposing view stated",
116
+ "counter_steelmanned": "Counterargument presented fairly",
117
+ "counter_refuted": "Substantive rebuttal provided",
118
+ "hook_present": "Introduction has an engaging hook",
119
+ "topic_sentences": "Each paragraph has a topic sentence",
120
+ "smooth_transitions": "Varied transitions between paragraphs",
121
+ "strong_conclusion": "Conclusion synthesizes (not just summarizes)",
122
+ "sentence_variety": "Varied sentence structures",
123
+ "precise_language": "Precise word choices (no vague terms)",
124
+ "no_repetition": "No excessive repetition",
125
+ "active_voice": "Predominantly active voice",
126
+ "no_filler": "No filler phrases",
127
+ }
128
+
129
+
130
+ def evaluate_essay(essay: str, model: str = "openai:gpt-4o") -> dict:
131
+ criteria_text = "\n".join([
132
+ f'{i+1}. "{name}": {desc}'
133
+ for i, (name, desc) in enumerate(EVALUATION_CRITERIA.items())
134
+ ])
135
+
136
+ prompt = f"""You are an EXTREMELY strict essay evaluator.
137
+
138
+ ESSAY:
139
+ \"\"\"
140
+ {essay}
141
+ \"\"\"
142
+
143
+ CRITERIA (1 ONLY if FULLY met, otherwise 0):
144
+ {criteria_text}
145
+
146
+ RULES: Be HARSH. A typical draft should score 40-60%. If unsure, score 0.
147
+
148
+ Respond in this JSON format:
149
+ {{
150
+ "thesis_clear": 0 or 1,
151
+ "thesis_debatable": 0 or 1,
152
+ "thesis_specific": 0 or 1,
153
+ "thesis_positioned": 0 or 1,
154
+ "multiple_reasons": 0 or 1,
155
+ "reasons_developed": 0 or 1,
156
+ "logical_progression": 0 or 1,
157
+ "no_logical_fallacies": 0 or 1,
158
+ "concrete_examples": 0 or 1,
159
+ "named_sources": 0 or 1,
160
+ "evidence_explained": 0 or 1,
161
+ "varied_evidence": 0 or 1,
162
+ "counter_acknowledged": 0 or 1,
163
+ "counter_steelmanned": 0 or 1,
164
+ "counter_refuted": 0 or 1,
165
+ "hook_present": 0 or 1,
166
+ "topic_sentences": 0 or 1,
167
+ "smooth_transitions": 0 or 1,
168
+ "strong_conclusion": 0 or 1,
169
+ "sentence_variety": 0 or 1,
170
+ "precise_language": 0 or 1,
171
+ "no_repetition": 0 or 1,
172
+ "active_voice": 0 or 1,
173
+ "no_filler": 0 or 1
174
+ }}
175
+
176
+ Return ONLY valid JSON."""
177
+
178
+ response = client.chat.completions.create(
179
+ model=model,
180
+ messages=[{"role": "user", "content": prompt}],
181
+ temperature=0,
182
+ )
183
+
184
+ try:
185
+ result = json.loads(response.choices[0].message.content)
186
+ except json.JSONDecodeError:
187
+ content = response.choices[0].message.content
188
+ result = json.loads(content[content.find('{'):content.rfind('}')+1])
189
+
190
+ for key in result:
191
+ result[key] = 1 if result[key] else 0
192
+
193
+ result["total_score"] = sum(v for k, v in result.items() if k in EVALUATION_CRITERIA)
194
+ result["max_score"] = len(EVALUATION_CRITERIA)
195
+ result["percentage"] = round(100 * result["total_score"] / result["max_score"], 1)
196
+
197
+ return result
198
+
199
+
200
+ def format_evaluation(eval_result: dict) -> str:
201
+ categories = {
202
+ "Thesis": ["thesis_clear", "thesis_debatable", "thesis_specific", "thesis_positioned"],
203
+ "Argument Depth": ["multiple_reasons", "reasons_developed", "logical_progression", "no_logical_fallacies"],
204
+ "Evidence Quality": ["concrete_examples", "named_sources", "evidence_explained", "varied_evidence"],
205
+ "Counterarguments": ["counter_acknowledged", "counter_steelmanned", "counter_refuted"],
206
+ "Structure": ["hook_present", "topic_sentences", "smooth_transitions", "strong_conclusion"],
207
+ "Writing Quality": ["sentence_variety", "precise_language", "no_repetition", "active_voice", "no_filler"],
208
+ }
209
+
210
+ lines = []
211
+ for cat, criteria in categories.items():
212
+ score = sum(eval_result[c] for c in criteria)
213
+ max_score = len(criteria)
214
+ checks = " ".join(["βœ…" if eval_result[c] else "❌" for c in criteria])
215
+ lines.append(f"{cat:<18} {checks} ({score}/{max_score})")
216
+
217
+ lines.append("-" * 50)
218
+ lines.append(f"TOTAL: {eval_result['total_score']}/{eval_result['max_score']} ({eval_result['percentage']}%)")
219
+
220
+ return "\n".join(lines)
221
+
222
+
223
+ def format_comparison(draft_eval: dict, revised_eval: dict) -> str:
224
+ categories = {
225
+ "Thesis": ["thesis_clear", "thesis_debatable", "thesis_specific", "thesis_positioned"],
226
+ "Argument Depth": ["multiple_reasons", "reasons_developed", "logical_progression", "no_logical_fallacies"],
227
+ "Evidence Quality": ["concrete_examples", "named_sources", "evidence_explained", "varied_evidence"],
228
+ "Counterarguments": ["counter_acknowledged", "counter_steelmanned", "counter_refuted"],
229
+ "Structure": ["hook_present", "topic_sentences", "smooth_transitions", "strong_conclusion"],
230
+ "Writing Quality": ["sentence_variety", "precise_language", "no_repetition", "active_voice", "no_filler"],
231
+ }
232
+
233
+ lines = []
234
+ lines.append(f"{'Category':<18} {'Draft':>10} {'Revised':>10} {'Change':>10}")
235
+ lines.append("=" * 50)
236
+
237
+ for cat, criteria in categories.items():
238
+ d = sum(draft_eval[c] for c in criteria)
239
+ r = sum(revised_eval[c] for c in criteria)
240
+ mx = len(criteria)
241
+ ch = r - d
242
+ ch_str = f"+{ch}" if ch > 0 else str(ch)
243
+ lines.append(f"{cat:<18} {d}/{mx}:>8 {r}/{mx}:>8 {ch_str:>10}")
244
+
245
+ lines.append("=" * 50)
246
+
247
+ # Fixed criteria
248
+ fixed = [c.replace("_", " ").title() for c in EVALUATION_CRITERIA
249
+ if draft_eval[c] == 0 and revised_eval[c] == 1]
250
+
251
+ imp = revised_eval["percentage"] - draft_eval["percentage"]
252
+ if imp > 0:
253
+ lines.append(f"\nπŸ“ˆ IMPROVEMENT: +{imp:.1f}%")
254
+ if fixed:
255
+ lines.append(f"\nβœ… Fixed criteria:")
256
+ for c in fixed:
257
+ lines.append(f" β€’ {c}")
258
+
259
+ return "\n".join(lines)
260
+
261
+
262
+ # ============================================================
263
+ # MAIN WORKFLOW FUNCTION
264
+ # ============================================================
265
+
266
+ def run_reflection_workflow(essay_prompt: str, progress=gr.Progress()):
267
+ """Run the complete workflow and return all outputs."""
268
+
269
+ progress(0.1, desc="πŸ“ Generating draft...")
270
+ draft = generate_draft(essay_prompt)
271
+
272
+ progress(0.3, desc="🧠 Reflecting on draft...")
273
+ feedback = reflect_on_draft(draft)
274
+
275
+ progress(0.5, desc="✍️ Revising draft...")
276
+ revised = revise_draft(draft, feedback)
277
+
278
+ progress(0.7, desc="πŸ“Š Evaluating draft...")
279
+ draft_eval = evaluate_essay(draft)
280
+
281
+ progress(0.85, desc="πŸ“Š Evaluating revision...")
282
+ revised_eval = evaluate_essay(revised)
283
+
284
+ progress(1.0, desc="βœ… Complete!")
285
+
286
+ # Format outputs
287
+ draft_eval_text = format_evaluation(draft_eval)
288
+ revised_eval_text = format_evaluation(revised_eval)
289
+ comparison_text = format_comparison(draft_eval, revised_eval)
290
+
291
+ return draft, feedback, revised, draft_eval_text, revised_eval_text, comparison_text
292
+
293
+
294
+ # ============================================================
295
+ # GRADIO INTERFACE
296
+ # ============================================================
297
+
298
+ with gr.Blocks(title="Reflective Writing Agent") as demo:
299
+
300
+ gr.Markdown("""
301
+ # πŸ€– Reflective Writing Agent
302
+ ### An Agentic AI Workflow: Draft β†’ Reflect β†’ Revise β†’ Evaluate
303
+
304
+ Enter an essay prompt and watch the AI write, critique, and improve an essay β€”
305
+ with quantified evaluation showing exactly what improved.
306
+ """)
307
+
308
+ with gr.Row():
309
+ with gr.Column(scale=3):
310
+ prompt_input = gr.Textbox(
311
+ label="Essay Prompt",
312
+ placeholder="e.g., Should social media platforms be regulated by the government?",
313
+ lines=2
314
+ )
315
+ with gr.Column(scale=1):
316
+ run_btn = gr.Button("πŸš€ Run Workflow", variant="primary", size="lg")
317
+
318
+ gr.Markdown("---")
319
+
320
+ with gr.Tabs():
321
+ with gr.TabItem("πŸ“ Step 1: Draft"):
322
+ draft_output = gr.Textbox(label="Initial Draft", lines=15)
323
+
324
+ with gr.TabItem("🧠 Step 2: Reflection"):
325
+ feedback_output = gr.Textbox(label="Feedback & Critique", lines=15)
326
+
327
+ with gr.TabItem("✍️ Step 3: Revision"):
328
+ revised_output = gr.Textbox(label="Revised Essay", lines=15)
329
+
330
+ with gr.TabItem("πŸ“Š Evaluation"):
331
+ with gr.Row():
332
+ with gr.Column():
333
+ gr.Markdown("### Draft Evaluation")
334
+ draft_eval_output = gr.Textbox(label="", lines=10)
335
+ with gr.Column():
336
+ gr.Markdown("### Revised Evaluation")
337
+ revised_eval_output = gr.Textbox(label="", lines=10)
338
+
339
+ gr.Markdown("### πŸ“ˆ Comparison")
340
+ comparison_output = gr.Textbox(label="", lines=12)
341
+
342
+ # Connect button to function
343
+ run_btn.click(
344
+ fn=run_reflection_workflow,
345
+ inputs=[prompt_input],
346
+ outputs=[draft_output, feedback_output, revised_output,
347
+ draft_eval_output, revised_eval_output, comparison_output]
348
+ )
349
+
350
+ # Example prompts
351
+ gr.Examples(
352
+ examples=[
353
+ ["Should social media platforms be regulated by the government?"],
354
+ ["Is artificial intelligence a threat to human employment?"],
355
+ ["Should college education be free for all students?"],
356
+ ["Are electric vehicles the solution to climate change?"],
357
+ ],
358
+ inputs=prompt_input
359
+ )
360
+
361
+ # Launch the app
362
+ if __name__ == "__main__":
363
+ demo.launch(theme=gr.themes.Soft(), share=True)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=6.0.0
2
+ aisuite
3
+ python-dotenv