banao-tech commited on
Commit
838dac3
Β·
verified Β·
1 Parent(s): 08ad3c7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +563 -424
app.py CHANGED
@@ -1,464 +1,603 @@
 
 
 
 
 
 
 
 
1
  import os
2
- import gradio as gr
 
 
 
3
  import anthropic
4
- from pypdf import PdfReader
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- # ── Anthropic client ──────────────────────────────────────────────────────────
7
- client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
 
 
 
 
8
 
9
- SYSTEM_PROMPT = """You are an expert problem analyst and innovation coach helping interns understand real-world problems and brainstorm creative solutions.
 
 
 
 
 
 
10
 
11
- When given a transcription or document, you will:
12
 
13
- 1. **PROBLEM UNDERSTANDING**
14
- - Identify and clearly articulate the core problem being discussed
15
- - Highlight key pain points, stakeholders, and context
16
- - Summarize what is known vs unknown about the problem
 
 
17
 
18
- 2. **ROOT CAUSE ANALYSIS**
19
- - Break down WHY this problem exists
20
- - Identify underlying causes (technical, organizational, process-related)
21
 
22
- 3. **BRAINSTORMING IDEAS**
23
- - Generate 5-8 diverse, creative solution directions
24
- - Include both quick wins and long-term strategic ideas
25
- - Think across technology, process, and people dimensions
26
 
27
- 4. **NEXT STEPS FOR INTERNS**
28
- - Suggest 3 concrete actions the intern can take immediately to explore this further
29
 
30
- Keep your tone encouraging, clear, and structured. Use headers and bullet points for readability."""
 
 
 
 
 
31
 
32
 
33
- def extract_text_from_pdf(pdf_file) -> str:
34
- reader = PdfReader(pdf_file)
35
- pages_text = []
36
- for page in reader.pages:
37
- text = page.extract_text()
38
- if text:
39
- pages_text.append(text.strip())
40
- return "\n\n".join(pages_text)
41
 
 
 
42
 
43
- def run_agent(text_input: str, pdf_file, progress=gr.Progress()):
44
- content = ""
 
45
 
46
- if pdf_file is not None:
47
- progress(0.1, desc="Reading PDF...")
48
- try:
49
- content = extract_text_from_pdf(pdf_file)
50
- if not content.strip():
51
- yield "Could not extract text from this PDF. It may be scanned or image-based. Please paste the text manually."
52
- return
53
- except Exception as e:
54
- yield f"Error reading PDF: {str(e)}"
55
- return
56
 
57
- elif text_input and text_input.strip():
58
- content = text_input.strip()
59
 
60
- else:
61
- yield "Please paste a transcription or upload a PDF file to get started."
62
- return
63
 
64
- if len(content) < 50:
65
- yield "The input seems too short. Please provide more context for a meaningful analysis."
66
- return
67
 
68
- MAX_CHARS = 12000
69
- if len(content) > MAX_CHARS:
70
- content = content[:MAX_CHARS] + "\n\n[... content truncated for length ...]"
71
 
72
- progress(0.3, desc="Analyzing problem...")
 
73
 
74
- user_message = f"""Here is the transcription / document content to analyze:
 
75
 
76
- ---
77
- {content}
78
- ---
79
 
80
- Please provide a full problem analysis and brainstorming session based on this content."""
 
 
 
 
81
 
82
- output = ""
83
- try:
84
- with client.messages.stream(
85
- model="claude-sonnet-4-6",
86
- max_tokens=2048,
87
- system=SYSTEM_PROMPT,
88
- messages=[{"role": "user", "content": user_message}],
89
- ) as stream:
90
- progress(0.5, desc="Generating insights...")
91
- for text_chunk in stream.text_stream:
92
- output += text_chunk
93
- yield output
94
 
95
- except anthropic.AuthenticationError:
96
- yield "Invalid API key. Please set your ANTHROPIC_API_KEY in the Space secrets."
97
- except anthropic.RateLimitError:
98
- yield "Rate limit reached. Please wait a moment and try again."
99
- except Exception as e:
100
- yield f"Unexpected error: {str(e)}"
101
-
102
-
103
- # ── Custom CSS ────────────────────────────────────────────────────────────────
104
- custom_css = """
105
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap');
106
-
107
- * {
108
- font-family: 'Inter', sans-serif !important;
109
- box-sizing: border-box;
110
- }
111
-
112
- body, .gradio-container {
113
- background-color: #ffffff !important;
114
- color: #111827 !important;
115
- }
116
-
117
- .gradio-container {
118
- max-width: 1280px !important;
119
- margin: 0 auto !important;
120
- padding: 0 2rem !important;
121
- }
122
-
123
- /* ── Header ── */
124
- #app-header {
125
- padding: 2.75rem 0 2rem;
126
- border-bottom: 1px solid #e5e7eb;
127
- margin-bottom: 2rem;
128
- }
129
-
130
- #app-header h1 {
131
- font-size: 1.5rem;
132
- font-weight: 600;
133
- color: #111827;
134
- letter-spacing: -0.025em;
135
- margin: 0 0 0.35rem 0;
136
- line-height: 1.3;
137
- }
138
-
139
- #app-header p {
140
- font-size: 0.875rem;
141
- color: #6b7280;
142
- margin: 0;
143
- font-weight: 400;
144
- line-height: 1.5;
145
- }
146
-
147
- #header-tag {
148
- display: inline-block;
149
- font-size: 0.7rem;
150
- font-weight: 600;
151
- letter-spacing: 0.1em;
152
- text-transform: uppercase;
153
- color: #2563eb;
154
- background: #eff6ff;
155
- border: 1px solid #bfdbfe;
156
- border-radius: 4px;
157
- padding: 0.2rem 0.6rem;
158
- margin-bottom: 0.85rem;
159
- }
160
-
161
- /* ── Panels ── */
162
- .input-panel, .output-panel {
163
- background: #ffffff !important;
164
- border: 1px solid #e5e7eb !important;
165
- border-radius: 10px !important;
166
- padding: 1.5rem !important;
167
- }
168
-
169
- /* ── Section label ── */
170
- .section-label {
171
- font-size: 0.7rem !important;
172
- font-weight: 600 !important;
173
- text-transform: uppercase !important;
174
- letter-spacing: 0.1em !important;
175
- color: #9ca3af !important;
176
- margin: 0 0 1rem 0 !important;
177
- }
178
-
179
- /* ── Textarea ── */
180
- textarea {
181
- background: #f9fafb !important;
182
- border: 1px solid #e5e7eb !important;
183
- border-radius: 8px !important;
184
- color: #111827 !important;
185
- font-size: 0.875rem !important;
186
- line-height: 1.75 !important;
187
- padding: 0.875rem 1rem !important;
188
- resize: vertical !important;
189
- transition: border-color 0.15s, box-shadow 0.15s !important;
190
- }
191
-
192
- textarea:focus {
193
- border-color: #2563eb !important;
194
- outline: none !important;
195
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.08) !important;
196
- background: #ffffff !important;
197
- }
198
-
199
- textarea::placeholder {
200
- color: #d1d5db !important;
201
- }
202
-
203
- /* ── File upload ── */
204
- .gr-file {
205
- background: #f9fafb !important;
206
- border: 1px dashed #d1d5db !important;
207
- border-radius: 8px !important;
208
- color: #6b7280 !important;
209
- }
210
-
211
- /* ── Tabs ── */
212
- .tab-nav {
213
- border-bottom: 1px solid #e5e7eb !important;
214
- margin-bottom: 1rem !important;
215
- background: transparent !important;
216
- }
217
-
218
- .tab-nav button {
219
- font-size: 0.8rem !important;
220
- font-weight: 500 !important;
221
- color: #9ca3af !important;
222
- border: none !important;
223
- background: transparent !important;
224
- padding: 0.5rem 1rem 0.6rem !important;
225
- border-radius: 0 !important;
226
- letter-spacing: 0.01em !important;
227
- transition: color 0.15s !important;
228
- }
229
-
230
- .tab-nav button:hover {
231
- color: #374151 !important;
232
- }
233
-
234
- .tab-nav button.selected {
235
- color: #2563eb !important;
236
- border-bottom: 2px solid #2563eb !important;
237
- font-weight: 600 !important;
238
- }
239
-
240
- /* ── Run button ── */
241
- #run-btn {
242
- background: #2563eb !important;
243
- color: #ffffff !important;
244
- border: none !important;
245
- border-radius: 8px !important;
246
- font-size: 0.875rem !important;
247
- font-weight: 600 !important;
248
- padding: 0.7rem 1.5rem !important;
249
- letter-spacing: 0.01em !important;
250
- transition: background 0.15s ease, box-shadow 0.15s ease !important;
251
- margin-top: 0.875rem !important;
252
- width: 100% !important;
253
- cursor: pointer !important;
254
- }
255
-
256
- #run-btn:hover {
257
- background: #1d4ed8 !important;
258
- box-shadow: 0 2px 8px rgba(37, 99, 235, 0.25) !important;
259
- }
260
-
261
- /* ── Output area ── */
262
- #output-area {
263
- background: #f9fafb !important;
264
- border: 1px solid #e5e7eb !important;
265
- border-radius: 8px !important;
266
- padding: 1.25rem 1.5rem !important;
267
- min-height: 420px !important;
268
- font-size: 0.875rem !important;
269
- line-height: 1.8 !important;
270
- color: #374151 !important;
271
- }
272
-
273
- #output-area h1, #output-area h2, #output-area h3 {
274
- color: #111827 !important;
275
- font-weight: 600 !important;
276
- letter-spacing: -0.015em !important;
277
- margin-top: 1.5rem !important;
278
- margin-bottom: 0.5rem !important;
279
- }
280
-
281
- #output-area h2 {
282
- font-size: 1rem !important;
283
- border-bottom: 1px solid #f3f4f6 !important;
284
- padding-bottom: 0.4rem !important;
285
- }
286
-
287
- #output-area strong {
288
- color: #111827 !important;
289
- font-weight: 600 !important;
290
- }
291
-
292
- #output-area ul, #output-area ol {
293
- padding-left: 1.25rem !important;
294
- color: #4b5563 !important;
295
- margin: 0.4rem 0 !important;
296
- }
297
-
298
- #output-area li {
299
- margin-bottom: 0.25rem !important;
300
- }
301
-
302
- #output-area p {
303
- color: #4b5563 !important;
304
- }
305
-
306
- #output-area em {
307
- color: #9ca3af !important;
308
- }
309
-
310
- /* ── Accordion ── */
311
- .gr-accordion {
312
- background: #f9fafb !important;
313
- border: 1px solid #e5e7eb !important;
314
- border-radius: 8px !important;
315
- margin-top: 0.875rem !important;
316
- }
317
-
318
- .gr-accordion .label-wrap span {
319
- font-size: 0.8rem !important;
320
- color: #6b7280 !important;
321
- font-weight: 500 !important;
322
- }
323
-
324
- /* ── Labels ── */
325
- label, .gr-label {
326
- color: #6b7280 !important;
327
- font-size: 0.8rem !important;
328
- font-weight: 500 !important;
329
- }
330
-
331
- /* ── Examples ── */
332
- .gr-examples {
333
- margin-top: 1rem !important;
334
- }
335
-
336
- .gr-examples table {
337
- background: #ffffff !important;
338
- border: 1px solid #e5e7eb !important;
339
- border-radius: 8px !important;
340
- overflow: hidden !important;
341
- }
342
-
343
- .gr-examples td {
344
- color: #6b7280 !important;
345
- font-size: 0.8rem !important;
346
- padding: 0.6rem 1rem !important;
347
- border-bottom: 1px solid #f3f4f6 !important;
348
- }
349
-
350
- .gr-examples tr:hover td {
351
- background: #f9fafb !important;
352
- cursor: pointer !important;
353
- }
354
-
355
- /* ── Divider ── */
356
- .divider {
357
- border: none !important;
358
- border-top: 1px solid #e5e7eb !important;
359
- margin: 2rem 0 1.5rem !important;
360
- }
361
-
362
- /* ── Scrollbar ── */
363
- ::-webkit-scrollbar { width: 5px; }
364
- ::-webkit-scrollbar-track { background: #f9fafb; }
365
- ::-webkit-scrollbar-thumb { background: #e5e7eb; border-radius: 4px; }
366
- ::-webkit-scrollbar-thumb:hover { background: #d1d5db; }
367
  """
368
 
369
- # ── Gradio UI ─────────────────────────────────────────────────────────────────
370
- with gr.Blocks(
371
- title="Problem Brainstormer",
372
- css=custom_css,
373
- ) as demo:
374
-
375
- gr.HTML("""
376
- <div id="app-header">
377
- <div id="header-tag">Intern Tool</div>
378
- <h1>Problem Brainstormer</h1>
379
- <p>Paste a meeting transcript or upload a document to get structured problem analysis and solution ideas.</p>
380
- </div>
381
- """)
382
-
383
- with gr.Row(equal_height=False):
384
- with gr.Column(scale=1, elem_classes=["input-panel"]):
385
- gr.HTML('<p class="section-label">Input</p>')
386
-
387
- with gr.Tabs():
388
- with gr.Tab("Paste Text"):
389
- text_input = gr.Textbox(
390
- label="",
391
- placeholder="Paste your meeting notes, transcript, or problem description here...",
392
- lines=16,
393
- max_lines=32,
394
- )
395
-
396
- with gr.Tab("Upload PDF"):
397
- pdf_input = gr.File(
398
- label="",
399
- file_types=[".pdf"],
400
- file_count="single",
401
- )
402
- gr.HTML('<p style="font-size:0.75rem;color:#9ca3af;margin-top:0.5rem">Text-based PDFs only. Scanned documents are not supported.</p>')
403
-
404
- run_btn = gr.Button(
405
- "Analyze and Brainstorm",
406
- variant="primary",
407
- elem_id="run-btn",
408
- )
409
 
410
- with gr.Accordion("How to use", open=False):
411
- gr.Markdown("""
412
- **Paste Text** β€” Copy any transcript, meeting notes, or problem statement into the text field.
 
 
 
413
 
414
- **Upload PDF** β€” Upload a text-based PDF such as a spec, report, or research document.
 
 
 
415
 
416
- **Click Analyze** to receive:
417
- - A clear articulation of the core problem
418
- - Root cause breakdown
419
- - 5–8 creative solution directions
420
- - Concrete next steps you can take as an intern
421
- """)
422
 
423
- with gr.Column(scale=1, elem_classes=["output-panel"]):
424
- gr.HTML('<p class="section-label">Analysis</p>')
425
- output = gr.Markdown(
426
- value="*Your analysis will appear here once you submit.*",
427
- elem_id="output-area",
428
- )
429
 
430
- gr.HTML('<hr class="divider">')
431
- gr.HTML('<p class="section-label">Example Inputs</p>')
432
-
433
- gr.Examples(
434
- examples=[
435
- [
436
- "In today's standup, the team discussed that customers are complaining that the onboarding flow takes too long. New users drop off after the third step in the sign-up process. We don't have clear data on exactly which step causes the most drop-off. The product team thinks it might be the email verification step but engineering says it could be the profile setup form. We have about 2 weeks before the next release and the CEO wants this fixed before the investor demo.",
437
- None,
438
- ],
439
- [
440
- "Meeting transcript: So the main issue we're facing is that our data pipeline fails every Friday evening when batch jobs run. The on-call engineer has to manually restart it each time. It's been happening for three months. We've tried looking at the logs but they're not detailed enough. The infrastructure team is overwhelmed and can't prioritize it. Meanwhile the analytics team can't get their Monday morning reports on time which is affecting business decisions.",
441
- None,
442
- ],
443
- ],
444
- inputs=[text_input, pdf_input],
445
- label="",
446
  )
447
 
448
- run_btn.click(
449
- fn=run_agent,
450
- inputs=[text_input, pdf_input],
451
- outputs=output,
452
- show_progress="full",
 
453
  )
454
 
455
- text_input.submit(
456
- fn=run_agent,
457
- inputs=[text_input, pdf_input],
458
- outputs=output,
459
- show_progress="full",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  )
461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
 
 
463
  if __name__ == "__main__":
464
- demo.launch()
 
 
1
+ """
2
+ Intern Problem-Solving API
3
+ Multi-agent FastAPI backend for structured problem analysis and solution generation.
4
+ Agents: Analyst β†’ Root Cause β†’ Solution Brainstorm β†’ Action Planner β†’ PDF Generator
5
+ """
6
+
7
+ import io
8
+ import json
9
  import os
10
+ import re
11
+ from datetime import datetime
12
+ from typing import Optional
13
+
14
  import anthropic
15
+ from fastapi import FastAPI, HTTPException
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.responses import StreamingResponse
18
+ from pydantic import BaseModel
19
+ from reportlab.lib import colors
20
+ from reportlab.lib.pagesizes import A4
21
+ from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
22
+ from reportlab.lib.units import mm
23
+ from reportlab.platypus import (
24
+ HRFlowable,
25
+ Paragraph,
26
+ SimpleDocTemplate,
27
+ Spacer,
28
+ Table,
29
+ TableStyle,
30
+ )
31
 
32
+ # ── App Setup ─────────────────────────────────────────────────────────────────
33
+ app = FastAPI(
34
+ title="Intern Problem-Solving API",
35
+ description="Multi-agent system: Analyst β†’ Root Cause β†’ Solutions β†’ Action Plan",
36
+ version="1.0.0",
37
+ )
38
 
39
+ app.add_middleware(
40
+ CORSMiddleware,
41
+ allow_origins=["*"],
42
+ allow_credentials=True,
43
+ allow_methods=["*"],
44
+ allow_headers=["*"],
45
+ )
46
 
47
+ client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY", ""))
48
 
49
+ # ── Request / Response Models ─────────────────────────────────────────────────
50
+ class ProblemInput(BaseModel):
51
+ content: str
52
+ intern_name: Optional[str] = "Intern"
53
+ intern_role: Optional[str] = "AI Developer Intern"
54
+ intern_goal: Optional[str] = ""
55
 
 
 
 
56
 
57
+ class AgentOutput(BaseModel):
58
+ agent: str
59
+ output: str
 
60
 
 
 
61
 
62
+ class FullAnalysis(BaseModel):
63
+ problem_statement: str
64
+ root_causes: str
65
+ solutions: str
66
+ action_plan: str
67
+ thinking_feedback: str
68
 
69
 
70
+ # ── Agent Definitions ─────────────────────────────────────────────────────────
 
 
 
 
 
 
 
71
 
72
+ AGENT_ANALYST = """You are the Problem Analyst Agent.
73
+ Your ONLY job: read the input and produce a crisp, structured problem statement.
74
 
75
+ Output format (use these exact headers):
76
+ ## Core Problem
77
+ One clear sentence: who has what problem, in what context.
78
 
79
+ ## Key Pain Points
80
+ - Bullet each distinct pain point (max 5)
 
 
 
 
 
 
 
 
81
 
82
+ ## Stakeholders
83
+ - Who is affected and how
84
 
85
+ ## Known vs Unknown
86
+ - Known: what facts are clear
87
+ - Unknown: what gaps exist
88
 
89
+ Keep it factual. No solutions yet. Max 250 words."""
 
 
90
 
 
 
 
91
 
92
+ AGENT_ROOT_CAUSE = """You are the Root Cause Analysis Agent.
93
+ You receive a problem statement. Your job: find WHY it exists.
94
 
95
+ Output format:
96
+ ## Root Cause Analysis
97
 
98
+ ### Immediate Cause
99
+ What is the visible trigger of the problem?
 
100
 
101
+ ### Underlying Causes
102
+ Break down causes across three lenses:
103
+ - **Technical**: systems, tools, architecture issues
104
+ - **Process**: workflow, communication, or procedural gaps
105
+ - **People/Skills**: knowledge gaps, habits, capacity issues
106
 
107
+ ### The Real Root Cause
108
+ One sentence: the deepest cause everything traces back to.
 
 
 
 
 
 
 
 
 
 
109
 
110
+ Be specific. Use "because" chains to trace causes deeper. Max 200 words."""
111
+
112
+
113
+ AGENT_SOLUTIONS = """You are the Solution Brainstorm Agent.
114
+ You receive a problem + root cause. Generate diverse, creative solutions.
115
+
116
+ Output format:
117
+ ## Solution Ideas
118
+
119
+ ### Quick Wins (Do this week)
120
+ 1. **[Name]** β€” What it is + why it helps
121
+
122
+ ### Medium-Term Fixes (Do this month)
123
+ 2. **[Name]** β€” What it is + why it helps
124
+ 3. **[Name]** β€” What it is + why it helps
125
+
126
+ ### Strategic / Long-Term
127
+ 4. **[Name]** β€” What it is + why it helps
128
+ 5. **[Name]** β€” What it is + why it helps
129
+
130
+ ### Unconventional / Creative
131
+ 6. **[Name]** β€” Think outside the box
132
+ 7. **[Name]** β€” Wildcard idea
133
+
134
+ For each idea: name it, describe it in 1-2 sentences, state the trade-off.
135
+ Think across: AI tools, process redesign, automation, collaboration, education.
136
+ Max 300 words."""
137
+
138
+
139
+ AGENT_ACTION_PLANNER = """You are the Action Planner Agent.
140
+ You receive the full analysis. Your job: give the intern 3 concrete next actions.
141
+
142
+ Output format:
143
+ ## Your Next Steps
144
+
145
+ ### Action 1: [Do This Today]
146
+ **What exactly**: One sentence instruction
147
+ **How**: Step-by-step (3-4 steps max)
148
+ **Success looks like**: How you'll know it worked
149
+ **Time needed**: X hours
150
+
151
+ ### Action 2: [Do This Week]
152
+ **What exactly**: One sentence instruction
153
+ **How**: Step-by-step (3-4 steps max)
154
+ **Success looks like**: How you'll know it worked
155
+ **Time needed**: X hours
156
+
157
+ ### Action 3: [Do This Month]
158
+ **What exactly**: One sentence instruction
159
+ **How**: Step-by-step (3-4 steps max)
160
+ **Success looks like**: How you'll know it worked
161
+ **Time needed**: X hours
162
+
163
+ Be specific enough that the intern can start immediately. No vague advice."""
164
+
165
+
166
+ AGENT_THINKING_COACH = """You are the Thinking Coach Agent.
167
+ You are an encouraging but honest coach helping an intern grow.
168
+ You receive the original problem input + full analysis.
169
+
170
+ Output format:
171
+ ## Thinking Feedback
172
+
173
+ ### What You Got Right
174
+ - Specific things in how the problem was framed that show good thinking
175
+
176
+ ### Blind Spots to Watch
177
+ - Where the framing was shallow or missing something important
178
+ - Specific examples only β€” no generic observations
179
+
180
+ ### Are You Thinking Like a Problem Solver or Task Executor?
181
+ One honest assessment with evidence from their input.
182
+
183
+ ### One Big Shift
184
+ The single most important mindset or approach shift that will help this intern most.
185
+
186
+ ### For Your Next Meeting
187
+ 3 specific things to do differently next time you face this type of problem.
188
+
189
+ Keep it encouraging but honest. Max 250 words."""
190
+
191
+
192
+ # ── Core Agent Runner ─────────────────────────────────────────────────────────
193
+
194
+ def run_agent(system_prompt: str, user_content: str, max_tokens: int = 800) -> str:
195
+ """Run a single agent and return its text output."""
196
+ response = client.messages.create(
197
+ model="claude-sonnet-4-6",
198
+ max_tokens=max_tokens,
199
+ system=system_prompt,
200
+ messages=[{"role": "user", "content": user_content}],
201
+ )
202
+ return response.content[0].text
203
+
204
+
205
+ # ── Pipeline ──────────────────────────────────────────────────────────────────
206
+
207
+ def run_pipeline(content: str, name: str, role: str, goal: str) -> FullAnalysis:
208
+ """Run all 5 agents in sequence, passing outputs forward."""
209
+
210
+ context_header = f"""
211
+ Intern Name: {name}
212
+ Intern Role: {role}
213
+ Current Goal: {goal if goal else 'Not specified'}
214
+
215
+ --- INPUT ---
216
+ {content[:8000]}
217
+ --- END INPUT ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
  """
219
 
220
+ # Agent 1: Analyst
221
+ problem_statement = run_agent(
222
+ AGENT_ANALYST,
223
+ f"Analyze this problem:\n{context_header}",
224
+ max_tokens=600,
225
+ )
226
+
227
+ # Agent 2: Root Cause (receives problem statement)
228
+ root_causes = run_agent(
229
+ AGENT_ROOT_CAUSE,
230
+ f"Problem Statement:\n{problem_statement}\n\nOriginal input context:\n{content[:3000]}",
231
+ max_tokens=500,
232
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
 
234
+ # Agent 3: Solutions (receives problem + root causes)
235
+ solutions = run_agent(
236
+ AGENT_SOLUTIONS,
237
+ f"Problem Statement:\n{problem_statement}\n\nRoot Causes:\n{root_causes}",
238
+ max_tokens=700,
239
+ )
240
 
241
+ # Agent 4: Action Planner (receives everything so far)
242
+ action_plan = run_agent(
243
+ AGENT_ACTION_PLANNER,
244
+ f"""Intern Role: {role}\nIntern Goal: {goal}
245
 
246
+ Problem Statement:\n{problem_statement}
 
 
 
 
 
247
 
248
+ Root Causes:\n{root_causes}
 
 
 
 
 
249
 
250
+ Solutions:\n{solutions}""",
251
+ max_tokens=700,
252
+ )
253
+
254
+ # Agent 5: Thinking Coach (sees original input + full analysis)
255
+ thinking_feedback = run_agent(
256
+ AGENT_THINKING_COACH,
257
+ f"""Original Input from Intern:\n{content[:3000]}
258
+
259
+ Problem Analysis:\n{problem_statement}
260
+
261
+ Root Causes:\n{root_causes}""",
262
+ max_tokens=600,
 
 
 
263
  )
264
 
265
+ return FullAnalysis(
266
+ problem_statement=problem_statement,
267
+ root_causes=root_causes,
268
+ solutions=solutions,
269
+ action_plan=action_plan,
270
+ thinking_feedback=thinking_feedback,
271
  )
272
 
273
+
274
+ # ── PDF Generator ─────────────────────────────────────────────────────────────
275
+
276
+ def strip_markdown(text: str) -> str:
277
+ """Remove markdown bold/italic markers for plain PDF text."""
278
+ text = re.sub(r"\*\*(.*?)\*\*", r"\1", text)
279
+ text = re.sub(r"\*(.*?)\*", r"\1", text)
280
+ return text
281
+
282
+
283
+ def build_pdf(analysis: FullAnalysis, name: str, role: str) -> bytes:
284
+ """Generate a professional PDF report from the analysis."""
285
+ buf = io.BytesIO()
286
+
287
+ # ── Page setup
288
+ doc = SimpleDocTemplate(
289
+ buf,
290
+ pagesize=A4,
291
+ rightMargin=20 * mm,
292
+ leftMargin=20 * mm,
293
+ topMargin=22 * mm,
294
+ bottomMargin=22 * mm,
295
+ )
296
+
297
+ styles = getSampleStyleSheet()
298
+ W = A4[0] - 40 * mm # usable width
299
+
300
+ # ── Custom styles
301
+ s_title = ParagraphStyle(
302
+ "title",
303
+ parent=styles["Normal"],
304
+ fontSize=22,
305
+ fontName="Helvetica-Bold",
306
+ textColor=colors.HexColor("#1a1a2e"),
307
+ spaceAfter=4,
308
+ )
309
+ s_sub = ParagraphStyle(
310
+ "sub",
311
+ parent=styles["Normal"],
312
+ fontSize=11,
313
+ fontName="Helvetica",
314
+ textColor=colors.HexColor("#6b7280"),
315
+ spaceAfter=12,
316
+ )
317
+ s_section = ParagraphStyle(
318
+ "section",
319
+ parent=styles["Normal"],
320
+ fontSize=13,
321
+ fontName="Helvetica-Bold",
322
+ textColor=colors.HexColor("#2563eb"),
323
+ spaceBefore=16,
324
+ spaceAfter=6,
325
+ )
326
+ s_h3 = ParagraphStyle(
327
+ "h3",
328
+ parent=styles["Normal"],
329
+ fontSize=11,
330
+ fontName="Helvetica-Bold",
331
+ textColor=colors.HexColor("#374151"),
332
+ spaceBefore=8,
333
+ spaceAfter=3,
334
+ )
335
+ s_body = ParagraphStyle(
336
+ "body",
337
+ parent=styles["Normal"],
338
+ fontSize=10,
339
+ fontName="Helvetica",
340
+ textColor=colors.HexColor("#374151"),
341
+ leading=15,
342
+ spaceAfter=4,
343
+ )
344
+ s_label = ParagraphStyle(
345
+ "label",
346
+ parent=styles["Normal"],
347
+ fontSize=8,
348
+ fontName="Helvetica-Bold",
349
+ textColor=colors.white,
350
+ )
351
+
352
+ def agent_badge(label: str, color: str) -> Table:
353
+ """Small colored badge showing which agent produced this section."""
354
+ data = [[Paragraph(f"β—‰ {label}", s_label)]]
355
+ t = Table(data, colWidths=[W])
356
+ t.setStyle(
357
+ TableStyle(
358
+ [
359
+ ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor(color)),
360
+ ("TOPPADDING", (0, 0), (-1, -1), 5),
361
+ ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
362
+ ("LEFTPADDING", (0, 0), (-1, -1), 10),
363
+ ("ROUNDEDCORNERS", [4, 4, 4, 4]),
364
+ ]
365
+ )
366
+ )
367
+ return t
368
+
369
+ def render_markdown_block(md_text: str) -> list:
370
+ """Convert basic markdown to ReportLab flowables."""
371
+ flowables = []
372
+ for line in md_text.splitlines():
373
+ line = line.strip()
374
+ if not line:
375
+ flowables.append(Spacer(1, 4))
376
+ continue
377
+ if line.startswith("## "):
378
+ flowables.append(Paragraph(line[3:], s_section))
379
+ elif line.startswith("### "):
380
+ flowables.append(Paragraph(line[4:], s_h3))
381
+ elif line.startswith("- ") or line.startswith("* "):
382
+ clean = strip_markdown(line[2:])
383
+ flowables.append(Paragraph(f"β€’ {clean}", s_body))
384
+ elif re.match(r"^\d+\.", line):
385
+ clean = strip_markdown(line)
386
+ flowables.append(Paragraph(clean, s_body))
387
+ elif line.startswith("**") and line.endswith("**"):
388
+ flowables.append(Paragraph(line[2:-2], s_h3))
389
+ else:
390
+ clean = strip_markdown(line)
391
+ flowables.append(Paragraph(clean, s_body))
392
+ return flowables
393
+
394
+ story = []
395
+
396
+ # ── Header
397
+ story.append(Paragraph("Problem Analysis Report", s_title))
398
+ story.append(
399
+ Paragraph(
400
+ f"{name} Β· {role} Β· {datetime.now().strftime('%d %B %Y')}",
401
+ s_sub,
402
+ )
403
+ )
404
+ story.append(HRFlowable(width=W, thickness=1.5, color=colors.HexColor("#2563eb"), spaceAfter=10))
405
+
406
+ # ── Section 1: Problem Statement
407
+ story.append(agent_badge("AGENT 1 β€” Problem Analyst", "#1e3a5f"))
408
+ story.append(Spacer(1, 6))
409
+ story += render_markdown_block(analysis.problem_statement)
410
+
411
+ # ── Section 2: Root Cause
412
+ story.append(Spacer(1, 8))
413
+ story.append(agent_badge("AGENT 2 β€” Root Cause Analyst", "#1a4731"))
414
+ story.append(Spacer(1, 6))
415
+ story += render_markdown_block(analysis.root_causes)
416
+
417
+ # ── Section 3: Solutions
418
+ story.append(Spacer(1, 8))
419
+ story.append(agent_badge("AGENT 3 β€” Solution Brainstorm", "#4a1942"))
420
+ story.append(Spacer(1, 6))
421
+ story += render_markdown_block(analysis.solutions)
422
+
423
+ # ── Section 4: Action Plan
424
+ story.append(Spacer(1, 8))
425
+ story.append(agent_badge("AGENT 4 β€” Action Planner", "#7c2d12"))
426
+ story.append(Spacer(1, 6))
427
+ story += render_markdown_block(analysis.action_plan)
428
+
429
+ # ── Section 5: Thinking Coach
430
+ story.append(Spacer(1, 8))
431
+ story.append(agent_badge("AGENT 5 β€” Thinking Coach", "#312e81"))
432
+ story.append(Spacer(1, 6))
433
+ story += render_markdown_block(analysis.thinking_feedback)
434
+
435
+ # ── Footer note
436
+ story.append(Spacer(1, 16))
437
+ story.append(HRFlowable(width=W, thickness=0.5, color=colors.HexColor("#e5e7eb"), spaceAfter=6))
438
+ story.append(
439
+ Paragraph(
440
+ "Generated by the AI Intern Problem-Solving System Β· Confidential",
441
+ ParagraphStyle("footer", parent=styles["Normal"], fontSize=8, textColor=colors.HexColor("#9ca3af"), alignment=1),
442
+ )
443
  )
444
 
445
+ doc.build(story)
446
+ buf.seek(0)
447
+ return buf.read()
448
+
449
+
450
+ # ── API Endpoints ─────────────────────────────────────────────────────────────
451
+
452
+ @app.get("/")
453
+ def root():
454
+ return {
455
+ "service": "Intern Problem-Solving API",
456
+ "version": "1.0.0",
457
+ "agents": [
458
+ "1. Problem Analyst",
459
+ "2. Root Cause Analyst",
460
+ "3. Solution Brainstorm",
461
+ "4. Action Planner",
462
+ "5. Thinking Coach",
463
+ ],
464
+ "endpoints": {
465
+ "POST /analyze": "Run full 5-agent pipeline, returns JSON",
466
+ "POST /analyze/stream": "Stream analysis as server-sent events",
467
+ "POST /analyze/pdf": "Run pipeline + return downloadable PDF",
468
+ "GET /health": "Health check",
469
+ },
470
+ }
471
+
472
+
473
+ @app.get("/health")
474
+ def health():
475
+ return {"status": "ok", "timestamp": datetime.utcnow().isoformat()}
476
+
477
+
478
+ @app.post("/analyze", response_model=FullAnalysis)
479
+ def analyze(body: ProblemInput):
480
+ """Run full 5-agent pipeline. Returns structured JSON."""
481
+ if not body.content.strip():
482
+ raise HTTPException(status_code=400, detail="Content cannot be empty.")
483
+ if len(body.content) < 30:
484
+ raise HTTPException(status_code=400, detail="Content too short for meaningful analysis.")
485
+
486
+ try:
487
+ result = run_pipeline(
488
+ content=body.content,
489
+ name=body.intern_name or "Intern",
490
+ role=body.intern_role or "AI Developer Intern",
491
+ goal=body.intern_goal or "",
492
+ )
493
+ return result
494
+ except anthropic.AuthenticationError:
495
+ raise HTTPException(status_code=401, detail="Invalid Anthropic API key.")
496
+ except anthropic.RateLimitError:
497
+ raise HTTPException(status_code=429, detail="Rate limit reached. Please wait and retry.")
498
+ except Exception as e:
499
+ raise HTTPException(status_code=500, detail=str(e))
500
+
501
+
502
+ @app.post("/analyze/stream")
503
+ def analyze_stream(body: ProblemInput):
504
+ """Stream each agent's output as server-sent events (SSE)."""
505
+ if not body.content.strip():
506
+ raise HTTPException(status_code=400, detail="Content cannot be empty.")
507
+
508
+ def event_stream():
509
+ agents = [
510
+ ("analyst", AGENT_ANALYST, "Problem Analyst"),
511
+ ("root_cause", AGENT_ROOT_CAUSE, "Root Cause Analyst"),
512
+ ("solutions", AGENT_SOLUTIONS, "Solution Brainstorm"),
513
+ ("action_plan", AGENT_ACTION_PLANNER, "Action Planner"),
514
+ ("thinking", AGENT_THINKING_COACH, "Thinking Coach"),
515
+ ]
516
+
517
+ context = {
518
+ "content": body.content[:8000],
519
+ "name": body.intern_name or "Intern",
520
+ "role": body.intern_role or "AI Developer Intern",
521
+ "goal": body.intern_goal or "",
522
+ }
523
+ accumulated = {}
524
+
525
+ for key, system_prompt, label in agents:
526
+ # Send agent start event
527
+ yield f"data: {json.dumps({'event': 'agent_start', 'agent': key, 'label': label})}\n\n"
528
+
529
+ # Build context-aware prompt for this agent
530
+ if key == "analyst":
531
+ user_msg = f"Intern: {context['name']} | Role: {context['role']} | Goal: {context['goal']}\n\nAnalyze:\n{context['content']}"
532
+ elif key == "root_cause":
533
+ user_msg = f"Problem:\n{accumulated.get('analyst','')}\n\nOriginal:\n{context['content'][:2000]}"
534
+ elif key == "solutions":
535
+ user_msg = f"Problem:\n{accumulated.get('analyst','')}\n\nRoot Causes:\n{accumulated.get('root_cause','')}"
536
+ elif key == "action_plan":
537
+ user_msg = f"Role: {context['role']}\n\nProblem:\n{accumulated.get('analyst','')}\n\nCauses:\n{accumulated.get('root_cause','')}\n\nSolutions:\n{accumulated.get('solutions','')}"
538
+ else:
539
+ user_msg = f"Original Input:\n{context['content'][:2500]}\n\nProblem:\n{accumulated.get('analyst','')}\n\nCauses:\n{accumulated.get('root_cause','')}"
540
+
541
+ # Stream this agent's output
542
+ agent_text = ""
543
+ with client.messages.stream(
544
+ model="claude-sonnet-4-6",
545
+ max_tokens=800,
546
+ system=system_prompt,
547
+ messages=[{"role": "user", "content": user_msg}],
548
+ ) as stream:
549
+ for chunk in stream.text_stream:
550
+ agent_text += chunk
551
+ yield f"data: {json.dumps({'event': 'token', 'agent': key, 'text': chunk})}\n\n"
552
+
553
+ accumulated[key] = agent_text
554
+ yield f"data: {json.dumps({'event': 'agent_done', 'agent': key})}\n\n"
555
+
556
+ yield f"data: {json.dumps({'event': 'done'})}\n\n"
557
+
558
+ return StreamingResponse(
559
+ event_stream(),
560
+ media_type="text/event-stream",
561
+ headers={
562
+ "Cache-Control": "no-cache",
563
+ "X-Accel-Buffering": "no",
564
+ },
565
+ )
566
+
567
+
568
+ @app.post("/analyze/pdf")
569
+ def analyze_pdf(body: ProblemInput):
570
+ """Run full pipeline and return a downloadable PDF report."""
571
+ if not body.content.strip():
572
+ raise HTTPException(status_code=400, detail="Content cannot be empty.")
573
+
574
+ try:
575
+ analysis = run_pipeline(
576
+ content=body.content,
577
+ name=body.intern_name or "Intern",
578
+ role=body.intern_role or "AI Developer Intern",
579
+ goal=body.intern_goal or "",
580
+ )
581
+ pdf_bytes = build_pdf(
582
+ analysis,
583
+ name=body.intern_name or "Intern",
584
+ role=body.intern_role or "AI Developer Intern",
585
+ )
586
+ filename = f"problem_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
587
+ return StreamingResponse(
588
+ io.BytesIO(pdf_bytes),
589
+ media_type="application/pdf",
590
+ headers={"Content-Disposition": f'attachment; filename="{filename}"'},
591
+ )
592
+ except anthropic.AuthenticationError:
593
+ raise HTTPException(status_code=401, detail="Invalid Anthropic API key.")
594
+ except anthropic.RateLimitError:
595
+ raise HTTPException(status_code=429, detail="Rate limit. Please wait and retry.")
596
+ except Exception as e:
597
+ raise HTTPException(status_code=500, detail=str(e))
598
+
599
 
600
+ # ── Dev Runner ────────────────────────────────────────────────────────────────
601
  if __name__ == "__main__":
602
+ import uvicorn
603
+ uvicorn.run("main:app", host="0.0.0.0", port=7860, reload=True)