nglebm19 commited on
Commit
d33e170
·
1 Parent(s): 68a80a9

feat(agent1): replace placeholders with inferred condition via heuristics; add dynamic bias parsing\n\n- Add infer_biased_condition() to map case text to a plausible biased dx\n- Replace [primary condition] placeholders in diagnostician output\n- Structure Devil’s Advocate output (Critical Analysis + Identified Biases)\n- Parse dynamic biases in UI and display them clearly\n- Improve readability: enforce dark text color in panels

Browse files
Files changed (2) hide show
  1. agents.py +196 -138
  2. app.py +271 -237
agents.py CHANGED
@@ -1,73 +1,102 @@
1
  import torch
2
  from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM
3
  import logging
 
4
 
5
  # Configure logging
6
  logging.basicConfig(level=logging.INFO)
7
  logger = logging.getLogger(__name__)
8
 
9
  class MedicalAgentSystem:
10
- def __init__(self):
11
- """Initialize the medical agent system with models and pipelines."""
12
- self.model_name = "microsoft/DialoGPT-medium"
13
- self.tokenizer = None
14
- self.model = None
15
- self.generator = None
16
- self._load_models()
17
-
18
- def _load_models(self):
19
- """Load the language models and tokenizer."""
20
- try:
21
- logger.info(f"Loading model: {self.model_name}")
22
- self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
23
- self.model = AutoModelForCausalLM.from_pretrained(self.model_name)
24
-
25
- # Add padding token if not present
26
- if self.tokenizer.pad_token is None:
27
- self.tokenizer.pad_token = self.tokenizer.eos_token
28
-
29
- # Create text generation pipeline
30
- self.generator = pipeline(
31
- "text-generation",
32
- model=self.model,
33
- tokenizer=self.tokenizer,
34
- max_length=200,
35
- do_sample=True,
36
- temperature=0.7,
37
- pad_token_id=self.tokenizer.eos_token_id
38
- )
39
- logger.info("Models loaded successfully")
40
-
41
- except Exception as e:
42
- logger.error(f"Error loading models: {e}")
43
- # Fallback to simpler text generation
44
- self.generator = self._fallback_generator
45
-
46
- def _fallback_generator(self, prompt, max_length=100):
47
- """Fallback generator when models fail to load."""
48
- return [{"generated_text": f"{prompt} [Model unavailable - using fallback logic]"}]
49
-
50
- def _generate_response(self, prompt, max_length=150):
51
- """Generate response using the loaded model."""
52
- try:
53
- if self.generator:
54
- result = self.generator(prompt, max_length=max_length)
55
- return result[0]["generated_text"].replace(prompt, "").strip()
56
- else:
57
- return self._fallback_generator(prompt, max_length)[0]["generated_text"]
58
- except Exception as e:
59
- logger.error(f"Generation error: {e}")
60
- return f"[Generation error: {e}]"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  def diagnostician_agent(case_text):
63
- """
64
- Agent 1: Generates initial diagnosis with intentional bias.
65
- Bias: Anchoring on first symptoms, confirmation bias.
66
- """
67
- agent_system = MedicalAgentSystem()
68
-
69
- # Biased prompt that encourages anchoring and confirmation bias
70
- biased_prompt = f"""As a medical diagnostician, analyze this case and provide an initial diagnosis.
71
 
72
  Patient Case:
73
  {case_text}
@@ -75,23 +104,33 @@ Patient Case:
75
  Instructions: Focus on the most prominent initial symptoms mentioned. Consider previous medical history as highly relevant to current symptoms. Provide a confident, definitive diagnosis based on the most obvious indicators.
76
 
77
  Initial Diagnosis:"""
78
-
79
- response = agent_system._generate_response(biased_prompt)
80
-
81
- # Clean up response and ensure it's medical in nature
82
- if not response or response.startswith("["):
83
- response = "Based on the initial symptoms and medical history, I suspect this is a case of [primary condition]. The patient's previous medical issues and current symptoms strongly suggest this diagnosis."
84
-
85
- return response
 
 
 
 
 
 
 
 
 
 
86
 
87
  def devils_advocate_agent(case_text, diagnosis):
88
- """
89
- Agent 2: Critiques the initial diagnosis and identifies bias.
90
- Focus: Challenge assumptions, identify cured conditions, detect bias.
91
- """
92
- agent_system = MedicalAgentSystem()
93
-
94
- critique_prompt = f"""As a medical devil's advocate, critically review this diagnosis and identify potential biases and errors.
95
 
96
  Patient Case:
97
  {case_text}
@@ -99,26 +138,41 @@ Patient Case:
99
  Initial Diagnosis:
100
  {diagnosis}
101
 
102
- Instructions: Challenge the assumptions in this diagnosis. Identify any anchoring bias, confirmation bias, or overemphasis on previous conditions. Consider if previous medical issues are still relevant. Point out overlooked symptoms or alternative explanations.
 
 
 
 
103
 
104
- Critical Analysis:"""
105
-
106
- response = agent_system._generate_response(critique_prompt)
107
-
108
- # Clean up response
109
- if not response or response.startswith("["):
110
- response = "This diagnosis demonstrates several potential biases: 1) Anchoring on initial symptoms without considering the full picture, 2) Overemphasis on previous medical history that may not be relevant, 3) Confirmation bias in interpreting current symptoms. Alternative explanations should be considered."
111
-
112
- return response
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  def synthesizer_agent(case_text, diagnosis, critique):
115
- """
116
- Agent 3: Synthesizes the diagnosis and critique into improved final diagnosis.
117
- Approach: Evidence-based, balanced synthesis addressing identified biases.
118
- """
119
- agent_system = MedicalAgentSystem()
120
-
121
- synthesis_prompt = f"""As a medical synthesizer, create a balanced, evidence-based final diagnosis by combining the initial diagnosis and critical analysis.
122
 
123
  Patient Case:
124
  {case_text}
@@ -126,55 +180,59 @@ Patient Case:
126
  Initial Diagnosis:
127
  {diagnosis}
128
 
129
- Critical Analysis:
130
  {critique}
131
 
132
- Instructions: Synthesize these perspectives into a balanced final diagnosis. Address all identified biases. Consider both the initial assessment and the critique. Provide a comprehensive, evidence-based conclusion that incorporates all relevant information.
133
-
134
- Final Diagnosis:"""
135
-
136
- response = agent_system._generate_response(synthesis_prompt)
137
-
138
- # Clean up response
139
- if not response or response.startswith("["):
140
- response = "After synthesizing the initial diagnosis and critical analysis, the final diagnosis is: [comprehensive diagnosis]. This conclusion addresses the identified biases by [specific improvements] and provides a balanced assessment based on all available evidence."
141
-
142
- return response
 
 
 
 
143
 
144
  # Convenience function to run the complete chain
145
  def run_medical_analysis(case_text):
146
- """
147
- Run the complete three-agent medical analysis chain.
148
-
149
- Args:
150
- case_text (str): Medical case description
151
-
152
- Returns:
153
- dict: Results from all three agents
154
- """
155
- try:
156
- # Agent 1: Initial diagnosis
157
- initial_diagnosis = diagnostician_agent(case_text)
158
-
159
- # Agent 2: Devil's advocate critique
160
- critique = devils_advocate_agent(case_text, initial_diagnosis)
161
-
162
- # Agent 3: Final synthesis
163
- final_diagnosis = synthesizer_agent(case_text, initial_diagnosis, critique)
164
-
165
- return {
166
- "initial_diagnosis": initial_diagnosis,
167
- "critique": critique,
168
- "final_diagnosis": final_diagnosis,
169
- "status": "success"
170
- }
171
-
172
- except Exception as e:
173
- logger.error(f"Error in medical analysis chain: {e}")
174
- return {
175
- "initial_diagnosis": "Error generating diagnosis",
176
- "critique": "Error generating critique",
177
- "final_diagnosis": "Error generating final diagnosis",
178
- "status": "error",
179
- "error": str(e)
180
- }
 
1
  import torch
2
  from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM
3
  import logging
4
+ import re
5
 
6
  # Configure logging
7
  logging.basicConfig(level=logging.INFO)
8
  logger = logging.getLogger(__name__)
9
 
10
  class MedicalAgentSystem:
11
+ def __init__(self):
12
+ """Initialize the medical agent system with models and pipelines."""
13
+ self.model_name = "microsoft/DialoGPT-medium"
14
+ self.tokenizer = None
15
+ self.model = None
16
+ self.generator = None
17
+ self._load_models()
18
+
19
+ def _load_models(self):
20
+ """Load the language models and tokenizer."""
21
+ try:
22
+ logger.info(f"Loading model: {self.model_name}")
23
+ self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
24
+ self.model = AutoModelForCausalLM.from_pretrained(self.model_name)
25
+
26
+ # Add padding token if not present
27
+ if self.tokenizer.pad_token is None:
28
+ self.tokenizer.pad_token = self.tokenizer.eos_token
29
+
30
+ # Create text generation pipeline
31
+ self.generator = pipeline(
32
+ "text-generation",
33
+ model=self.model,
34
+ tokenizer=self.tokenizer,
35
+ max_length=200,
36
+ do_sample=True,
37
+ temperature=0.7,
38
+ pad_token_id=self.tokenizer.eos_token_id
39
+ )
40
+ logger.info("Models loaded successfully")
41
+
42
+ except Exception as e:
43
+ logger.error(f"Error loading models: {e}")
44
+ # Fallback to simpler text generation
45
+ self.generator = self._fallback_generator
46
+
47
+ def _fallback_generator(self, prompt, max_length=100):
48
+ """Fallback generator when models fail to load."""
49
+ return [{"generated_text": f"{prompt} [Model unavailable - using fallback logic]"}]
50
+
51
+ def _generate_response(self, prompt, max_length=150):
52
+ """Generate response using the loaded model."""
53
+ try:
54
+ if self.generator:
55
+ result = self.generator(prompt, max_length=max_length)
56
+ return result[0]["generated_text"].replace(prompt, "").strip()
57
+ else:
58
+ return self._fallback_generator(prompt, max_length)[0]["generated_text"]
59
+ except Exception as e:
60
+ logger.error(f"Generation error: {e}")
61
+ return f"[Generation error: {e}]"
62
+
63
+
64
+ def infer_biased_condition(case_text: str) -> str:
65
+ """Infer a plausible biased primary condition from the case text using simple heuristics.
66
+ This intentionally leans toward common anchoring/confirmation patterns.
67
+ """
68
+ text = case_text.lower()
69
+ # Case 1 style: RLQ pain, appendectomy history → anchoring to appendicitis
70
+ if ("right lower quadrant" in text or "rlq" in text or "append" in text) and "pain" in text:
71
+ return "acute appendicitis"
72
+ # Case 2 style: dyspnea/chest tightness + prior MI/HTN/DM → anchoring to unstable angina/ACS
73
+ if ("shortness of breath" in text or "dyspnea" in text or "chest tightness" in text or "chest pain" in text) and ("myocardial infarction" in text or "stent" in text or "coronary" in text or "heart" in text):
74
+ return "unstable angina (acute coronary syndrome)"
75
+ # If many respiratory clues but with cardiac history still anchor to cardiac
76
+ if ("shortness of breath" in text or "chest tightness" in text) and ("hypertension" in text or "diabetes" in text):
77
+ return "congestive heart failure exacerbation"
78
+ # Case 3 style: fatigue + joint pain after URI/strep → availability to post-strep/viral
79
+ if ("fatigue" in text and ("joint pain" in text or "arthralgia" in text)) and ("upper respiratory" in text or "uri" in text or "streptococcal" in text or "strep" in text):
80
+ return "post-streptococcal reactive arthritis"
81
+ # Case 4 style: low back pain radiating, positive SLR → anchor to chronic DDD/sciatica
82
+ if ("back pain" in text or "lumbar" in text) and ("radiates" in text or "radicul" in text or "straight leg raise" in text):
83
+ return "lumbar radiculopathy (sciatica)"
84
+ # Generic anchors
85
+ if "fever" in text and "cough" in text:
86
+ return "community-acquired pneumonia"
87
+ if "abdominal pain" in text and "nausea" in text:
88
+ return "gastroenteritis"
89
+ return "most likely condition based on prominent symptoms"
90
 
91
  def diagnostician_agent(case_text):
92
+ """
93
+ Agent 1: Generates initial diagnosis with intentional bias.
94
+ Bias: Anchoring on first symptoms, confirmation bias.
95
+ """
96
+ agent_system = MedicalAgentSystem()
97
+
98
+ # Biased prompt that encourages anchoring and confirmation bias
99
+ biased_prompt = f"""As a medical diagnostician, analyze this case and provide an initial diagnosis.
100
 
101
  Patient Case:
102
  {case_text}
 
104
  Instructions: Focus on the most prominent initial symptoms mentioned. Consider previous medical history as highly relevant to current symptoms. Provide a confident, definitive diagnosis based on the most obvious indicators.
105
 
106
  Initial Diagnosis:"""
107
+
108
+ response = agent_system._generate_response(biased_prompt)
109
+
110
+ # Ensure a concrete condition name replaces any placeholder
111
+ condition = infer_biased_condition(case_text)
112
+
113
+ # Replace placeholder tokens if present
114
+ response = re.sub(r"\[\s*primary condition\s*\]", condition, response, flags=re.IGNORECASE)
115
+ response = response.replace("[condition]", condition)
116
+
117
+ # If the model produced nothing helpful, compose a biased sentence
118
+ if not response or response.startswith("[") or "[primary condition]" in response or len(response.split()) < 5:
119
+ response = (
120
+ f"Based on the initial symptoms and medical history, I suspect this is a case of {condition}. "
121
+ "The patient's previous medical issues and current symptoms strongly suggest this diagnosis."
122
+ )
123
+
124
+ return response
125
 
126
  def devils_advocate_agent(case_text, diagnosis):
127
+ """
128
+ Agent 2: Critiques the initial diagnosis and identifies bias.
129
+ Focus: Challenge assumptions, identify cured conditions, detect bias.
130
+ """
131
+ agent_system = MedicalAgentSystem()
132
+
133
+ critique_prompt = f"""You are a medical devil's advocate. Critically review the initial diagnosis and identify potential biases and errors.
134
 
135
  Patient Case:
136
  {case_text}
 
138
  Initial Diagnosis:
139
  {diagnosis}
140
 
141
+ Instructions:
142
+ - Challenge the assumptions in this diagnosis and evaluate alternative explanations.
143
+ - Explicitly determine whether any past conditions are likely resolved/cured and thus irrelevant.
144
+ - Identify specific cognitive biases by name if present from this list: Anchoring Bias, Confirmation Bias, Availability Bias, Overconfidence, Premature Closure, Representativeness, Base Rate Neglect, Search Satisficing.
145
+ - Produce your answer in the following two sections exactly:
146
 
147
+ Critical Analysis:
148
+ [Write a concise critique here]
149
+
150
+ Identified Biases:
151
+ - [Bias Name]: [One-line justification]
152
+ - [Bias Name]: [One-line justification]
153
+ (Only include items that truly apply. If none, write: None detected.)
154
+ """
155
+
156
+ response = agent_system._generate_response(critique_prompt)
157
+
158
+ # Clean up response
159
+ if not response or response.startswith("["):
160
+ response = (
161
+ "Critical Analysis: The initial diagnosis likely over-relies on prior history and the first symptoms. "
162
+ "Identified Biases:\n- Anchoring Bias: Emphasized earliest symptoms despite later conflicting signs.\n"
163
+ "- Confirmation Bias: Interpreted findings to support the prior condition without adequate differential consideration."
164
+ )
165
+
166
+ return response
167
 
168
  def synthesizer_agent(case_text, diagnosis, critique):
169
+ """
170
+ Agent 3: Synthesizes the diagnosis and critique into improved final diagnosis.
171
+ Approach: Evidence-based, balanced synthesis addressing identified biases.
172
+ """
173
+ agent_system = MedicalAgentSystem()
174
+
175
+ synthesis_prompt = f"""As a medical synthesizer, create a balanced, evidence-based final diagnosis by combining the initial diagnosis and critical analysis.
176
 
177
  Patient Case:
178
  {case_text}
 
180
  Initial Diagnosis:
181
  {diagnosis}
182
 
183
+ Critical Analysis and Identified Biases (from Devil's Advocate):
184
  {critique}
185
 
186
+ Instructions: Address the critique and the listed biases explicitly. Provide a differential diagnosis and a most likely diagnosis with justification, and list 2-3 next diagnostic steps.
187
+
188
+ Final Diagnosis:
189
+ """
190
+
191
+ response = agent_system._generate_response(synthesis_prompt)
192
+
193
+ # Clean up response
194
+ if not response or response.startswith("["):
195
+ response = (
196
+ "Final Diagnosis: [comprehensive diagnosis]. This integrates the critique by avoiding anchoring and confirmation, "
197
+ "and proposes next steps: [tests/interventions]."
198
+ )
199
+
200
+ return response
201
 
202
  # Convenience function to run the complete chain
203
  def run_medical_analysis(case_text):
204
+ """
205
+ Run the complete three-agent medical analysis chain.
206
+
207
+ Args:
208
+ case_text (str): Medical case description
209
+
210
+ Returns:
211
+ dict: Results from all three agents
212
+ """
213
+ try:
214
+ # Agent 1: Initial diagnosis
215
+ initial_diagnosis = diagnostician_agent(case_text)
216
+
217
+ # Agent 2: Devil's advocate critique
218
+ critique = devils_advocate_agent(case_text, initial_diagnosis)
219
+
220
+ # Agent 3: Final synthesis
221
+ final_diagnosis = synthesizer_agent(case_text, initial_diagnosis, critique)
222
+
223
+ return {
224
+ "initial_diagnosis": initial_diagnosis,
225
+ "critique": critique,
226
+ "final_diagnosis": final_diagnosis,
227
+ "status": "success"
228
+ }
229
+
230
+ except Exception as e:
231
+ logger.error(f"Error in medical analysis chain: {e}")
232
+ return {
233
+ "initial_diagnosis": "Error generating diagnosis",
234
+ "critique": "Error generating critique",
235
+ "final_diagnosis": "Error generating final diagnosis",
236
+ "status": "error",
237
+ "error": str(e)
238
+ }
app.py CHANGED
@@ -1,80 +1,107 @@
1
  import gradio as gr
2
  import time
 
3
  from agents import run_medical_analysis
4
  from cases import get_case_titles, get_case_description, get_bias_analysis
5
 
6
  # Global variable to store current analysis results
7
  current_results = None
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  def analyze_medical_case(case_input, custom_case_text=""):
10
- """
11
- Run the complete medical analysis using the three-agent system.
12
-
13
- Args:
14
- case_input (str): Selected case ID from dropdown
15
- custom_case_text (str): Custom case text input
16
-
17
- Returns:
18
- tuple: (case_display, agent1_output, agent2_output, agent3_output, bias_analysis)
19
- """
20
- global current_results
21
-
22
- # Determine which case to analyze
23
- if case_input == "custom" and custom_case_text.strip():
24
- case_text = custom_case_text.strip()
25
- case_display = f"**Custom Case:**\n\n{case_text}"
26
- bias_analysis = "Custom case - bias analysis will be generated by the agents"
27
- else:
28
- case_text = get_case_description(case_input)
29
- case_display = f"**{get_case_titles()[case_input]}**\n\n{case_text}"
30
- bias_info = get_bias_analysis(case_input)
31
- bias_analysis = f"**Expected Bias Types:** {bias_info['bias_type']}\n\n**Expected Bias Pattern:** {bias_info['expected_bias']}"
32
-
33
- # Run the three-agent analysis
34
- try:
35
- results = run_medical_analysis(case_text)
36
- current_results = results
37
-
38
- if results["status"] == "success":
39
- return (
40
- case_display,
41
- f"**Agent 1 (Diagnostician) - Initial Diagnosis:**\n\n{results['initial_diagnosis']}",
42
- f"**Agent 2 (Devil's Advocate) - Critical Analysis:**\n\n{results['critique']}",
43
- f"**Agent 3 (Synthesizer) - Final Diagnosis:**\n\n{results['final_diagnosis']}",
44
- bias_analysis
45
- )
46
- else:
47
- error_msg = f"Error in analysis: {results.get('error', 'Unknown error')}"
48
- return (
49
- case_display,
50
- f"**Error:** {error_msg}",
51
- f"**Error:** {error_msg}",
52
- f"**Error:** {error_msg}",
53
- bias_analysis
54
- )
55
-
56
- except Exception as e:
57
- error_msg = f"Unexpected error: {str(e)}"
58
- return (
59
- case_display,
60
- f"**Error:** {error_msg}",
61
- f"**Error:** {error_msg}",
62
- f"**Error:** {error_msg}",
63
- bias_analysis
64
- )
 
65
 
66
  def clear_analysis():
67
- """Clear all analysis outputs."""
68
- global current_results
69
- current_results = None
70
- return "", "", "", "", ""
71
 
72
  def get_learning_points():
73
- """Generate learning points based on the current analysis."""
74
- if not current_results or current_results.get("status") != "success":
75
- return "No analysis results available. Please run an analysis first."
76
-
77
- learning_points = """
78
  ## 🎯 Key Learning Points from This Analysis
79
 
80
  ### 1. **Bias Identification**
@@ -102,182 +129,189 @@ def get_learning_points():
102
  - **Agent 2**: Identifies and challenges biases
103
  - **Agent 3**: Synthesizes for improved final diagnosis
104
  """
105
- return learning_points
106
 
107
  # Create the Gradio interface
108
  def create_interface():
109
- """Create and configure the Gradio interface."""
110
-
111
- with gr.Blocks(
112
- title="Devil's Advocate Multi-Agent Medical Analysis System",
113
- theme=gr.themes.Soft(),
114
- css="""
115
- .bias-highlight {
116
- background-color: #fff3cd;
117
- border-left: 4px solid #ffc107;
118
- padding: 10px;
119
- margin: 10px 0;
120
- }
121
- .agent-output {
122
- background-color: #f8f9fa;
123
- border: 1px solid #dee2e6;
124
- border-radius: 5px;
125
- padding: 15px;
126
- margin: 10px 0;
127
- }
128
- .case-display {
129
- background-color: #e3f2fd;
130
- border: 1px solid #2196f3;
131
- border-radius: 5px;
132
- padding: 15px;
133
- margin: 10px 0;
134
- }
135
- """
136
- ) as interface:
137
-
138
- gr.Markdown("""
139
- # 🏥 Devil's Advocate Multi-Agent Medical Analysis System
140
-
141
- This demo shows how multiple AI agents can overcome diagnostic bias by simulating a clinical review process.
142
-
143
- ## How It Works:
144
- 1. **Agent 1 (Diagnostician)**: Provides initial diagnosis (may be biased)
145
- 2. **Agent 2 (Devil's Advocate)**: Critiques and identifies bias
146
- 3. **Agent 3 (Synthesizer)**: Creates improved final diagnosis
147
-
148
- ## Instructions:
149
- - Select a sample case or input your own medical case
150
- - Click "Run Analysis" to see the three-agent process
151
- - Observe how bias is identified and addressed
152
- """)
153
-
154
- with gr.Row():
155
- with gr.Column(scale=1):
156
- gr.Markdown("### 📋 Case Selection")
157
-
158
- # Case selection dropdown
159
- case_dropdown = gr.Dropdown(
160
- choices=["Select a case..."] + list(get_case_titles().keys()),
161
- label="Choose a Sample Case",
162
- value="Select a case...",
163
- interactive=True
164
- )
165
-
166
- # Custom case input
167
- custom_case = gr.Textbox(
168
- label="Or Input Custom Medical Case",
169
- placeholder="Describe the patient's symptoms, history, and examination findings...",
170
- lines=8,
171
- interactive=True
172
- )
173
-
174
- # Analysis button
175
- analyze_btn = gr.Button(
176
- "🔍 Run Analysis",
177
- variant="primary",
178
- size="lg"
179
- )
180
-
181
- # Clear button
182
- clear_btn = gr.Button(
183
- "🗑️ Clear Analysis",
184
- variant="secondary"
185
- )
186
-
187
- # Learning points button
188
- learning_btn = gr.Button(
189
- "📚 Show Learning Points",
190
- variant="secondary"
191
- )
192
-
193
- with gr.Column(scale=2):
194
- gr.Markdown("### 📊 Analysis Results")
195
-
196
- # Case display
197
- case_display = gr.Markdown(
198
- label="Case Information",
199
- elem_classes=["case-display"]
200
- )
201
-
202
- # Agent outputs
203
- agent1_output = gr.Markdown(
204
- label="Agent 1: Initial Diagnosis",
205
- elem_classes=["agent-output"]
206
- )
207
-
208
- agent2_output = gr.Markdown(
209
- label="Agent 2: Devil's Advocate Critique",
210
- elem_classes=["agent-output"]
211
- )
212
-
213
- agent3_output = gr.Markdown(
214
- label="Agent 3: Final Synthesis",
215
- elem_classes=["agent-output"]
216
- )
217
-
218
- # Bias analysis
219
- bias_analysis = gr.Markdown(
220
- label="Expected Bias Analysis",
221
- elem_classes=["bias-highlight"]
222
- )
223
-
224
- # Learning points
225
- learning_points = gr.Markdown(
226
- label="Learning Points",
227
- visible=False
228
- )
229
-
230
- # Event handlers
231
- analyze_btn.click(
232
- fn=analyze_medical_case,
233
- inputs=[case_dropdown, custom_case],
234
- outputs=[case_display, agent1_output, agent2_output, agent3_output, bias_analysis]
235
- )
236
-
237
- clear_btn.click(
238
- fn=clear_analysis,
239
- outputs=[case_display, agent1_output, agent2_output, agent3_output, bias_analysis]
240
- )
241
-
242
- learning_btn.click(
243
- fn=get_learning_points,
244
- outputs=learning_points
245
- )
246
-
247
- # Show learning points when button is clicked
248
- learning_btn.click(
249
- fn=lambda: gr.update(visible=True),
250
- outputs=learning_points
251
- )
252
-
253
- # Auto-select custom case when custom text is entered
254
- def on_custom_text_change(text):
255
- if text.strip():
256
- return "custom"
257
- return case_dropdown.value
258
-
259
- custom_case.change(
260
- fn=on_custom_text_change,
261
- inputs=custom_case,
262
- outputs=case_dropdown
263
- )
264
-
265
- gr.Markdown("""
266
- ---
267
- **Note**: This is a demonstration system for educational purposes.
268
- The AI agents simulate medical reasoning but should not be used for actual clinical decision-making.
269
- """)
270
-
271
- return interface
 
 
 
 
 
 
 
272
 
273
  # Main execution
274
  if __name__ == "__main__":
275
- # Create and launch the interface
276
- interface = create_interface()
277
- interface.launch(
278
- server_name="0.0.0.0",
279
- server_port=7860,
280
- share=False,
281
- show_error=True,
282
- quiet=False
283
- )
 
1
  import gradio as gr
2
  import time
3
+ import re
4
  from agents import run_medical_analysis
5
  from cases import get_case_titles, get_case_description, get_bias_analysis
6
 
7
  # Global variable to store current analysis results
8
  current_results = None
9
 
10
+ def extract_dynamic_biases(critique_text: str) -> str:
11
+ """Extract a dynamic list of biases from the Devil's Advocate output."""
12
+ if not critique_text:
13
+ return "No critique available."
14
+ # Look for the "Identified Biases:" section and parse bullet lines
15
+ section_match = re.search(r"Identified Biases:\n([\s\S]*)", critique_text)
16
+ if not section_match:
17
+ return "Identified Biases: None detected."
18
+ section = section_match.group(1)
19
+ # Stop at next empty line or section-like header
20
+ section = re.split(r"\n\s*\n|\n[A-Z][A-Za-z ]+:\n", section, maxsplit=1)[0]
21
+ bullets = []
22
+ for line in section.splitlines():
23
+ line = line.strip()
24
+ if line.startswith("-"):
25
+ bullets.append(line.lstrip("- "))
26
+ # Fallback if model wrote a single-line "None detected."
27
+ if not bullets:
28
+ if "none" in section.lower():
29
+ return "**Identified Biases:** None detected."
30
+ return "**Identified Biases:** Unable to parse."
31
+ # Render nicely
32
+ rendered = "\n".join([f"- {b}" for b in bullets])
33
+ return f"**Identified Biases (dynamic):**\n\n{rendered}"
34
+
35
  def analyze_medical_case(case_input, custom_case_text=""):
36
+ """
37
+ Run the complete medical analysis using the three-agent system.
38
+
39
+ Args:
40
+ case_input (str): Selected case ID from dropdown
41
+ custom_case_text (str): Custom case text input
42
+
43
+ Returns:
44
+ tuple: (case_display, agent1_output, agent2_output, agent3_output, bias_analysis)
45
+ """
46
+ global current_results
47
+
48
+ # Determine which case to analyze
49
+ if case_input == "custom" and custom_case_text.strip():
50
+ case_text = custom_case_text.strip()
51
+ case_display = f"**Custom Case:**\n\n{case_text}"
52
+ static_bias = ""
53
+ else:
54
+ case_text = get_case_description(case_input)
55
+ case_display = f"**{get_case_titles()[case_input]}**\n\n{case_text}"
56
+ bias_info = get_bias_analysis(case_input)
57
+ static_bias = f"\n\n> Static bias hint: {bias_info['bias_type']} {bias_info['expected_bias']}" if bias_info else ""
58
+
59
+ # Run the three-agent analysis
60
+ try:
61
+ results = run_medical_analysis(case_text)
62
+ current_results = results
63
+
64
+ if results["status"] == "success":
65
+ dynamic_bias = extract_dynamic_biases(results.get("critique", ""))
66
+ return (
67
+ case_display,
68
+ f"**Agent 1 (Diagnostician) - Initial Diagnosis:**\n\n{results['initial_diagnosis']}",
69
+ f"**Agent 2 (Devil's Advocate) - Critical Analysis:**\n\n{results['critique']}",
70
+ f"**Agent 3 (Synthesizer) - Final Diagnosis:**\n\n{results['final_diagnosis']}",
71
+ f"{dynamic_bias}{static_bias}"
72
+ )
73
+ else:
74
+ error_msg = f"Error in analysis: {results.get('error', 'Unknown error')}"
75
+ return (
76
+ case_display,
77
+ f"**Error:** {error_msg}",
78
+ f"**Error:** {error_msg}",
79
+ f"**Error:** {error_msg}",
80
+ ""
81
+ )
82
+
83
+ except Exception as e:
84
+ error_msg = f"Unexpected error: {str(e)}"
85
+ return (
86
+ case_display,
87
+ f"**Error:** {error_msg}",
88
+ f"**Error:** {error_msg}",
89
+ f"**Error:** {error_msg}",
90
+ ""
91
+ )
92
 
93
  def clear_analysis():
94
+ """Clear all analysis outputs."""
95
+ global current_results
96
+ current_results = None
97
+ return "", "", "", "", ""
98
 
99
  def get_learning_points():
100
+ """Generate learning points based on the current analysis."""
101
+ if not current_results or current_results.get("status") != "success":
102
+ return "No analysis results available. Please run an analysis first."
103
+
104
+ learning_points = """
105
  ## 🎯 Key Learning Points from This Analysis
106
 
107
  ### 1. **Bias Identification**
 
129
  - **Agent 2**: Identifies and challenges biases
130
  - **Agent 3**: Synthesizes for improved final diagnosis
131
  """
132
+ return learning_points
133
 
134
  # Create the Gradio interface
135
  def create_interface():
136
+ """Create and configure the Gradio interface."""
137
+
138
+ with gr.Blocks(
139
+ title="Devil's Advocate Multi-Agent Medical Analysis System",
140
+ theme=gr.themes.Soft(),
141
+ css="""
142
+ /* Ensure dark text for light panels */
143
+ .bias-highlight, .agent-output, .case-display {
144
+ color: #111 !important;
145
+ }
146
+ .bias-highlight * , .agent-output * , .case-display * {
147
+ color: inherit !important;
148
+ }
149
+ .bias-highlight {
150
+ background-color: #fff3cd;
151
+ border-left: 4px solid #ffc107;
152
+ padding: 10px;
153
+ margin: 10px 0;
154
+ }
155
+ .agent-output {
156
+ background-color: #f8f9fa;
157
+ border: 1px solid #dee2e6;
158
+ border-radius: 5px;
159
+ padding: 15px;
160
+ margin: 10px 0;
161
+ }
162
+ .case-display {
163
+ background-color: #e3f2fd;
164
+ border: 1px solid #2196f3;
165
+ border-radius: 5px;
166
+ padding: 15px;
167
+ margin: 10px 0;
168
+ }
169
+ """
170
+ ) as interface:
171
+
172
+ gr.Markdown("""
173
+ # 🏥 Devil's Advocate Multi-Agent Medical Analysis System
174
+
175
+ This demo shows how multiple AI agents can overcome diagnostic bias by simulating a clinical review process.
176
+
177
+ ## How It Works:
178
+ 1. **Agent 1 (Diagnostician)**: Provides initial diagnosis (may be biased)
179
+ 2. **Agent 2 (Devil's Advocate)**: Critiques and identifies bias
180
+ 3. **Agent 3 (Synthesizer)**: Creates improved final diagnosis
181
+
182
+ ## Instructions:
183
+ - Select a sample case or input your own medical case
184
+ - Click "Run Analysis" to see the three-agent process
185
+ - Observe how bias is identified and addressed
186
+ """)
187
+
188
+ with gr.Row():
189
+ with gr.Column(scale=1):
190
+ gr.Markdown("### 📋 Case Selection")
191
+
192
+ # Case selection dropdown
193
+ case_dropdown = gr.Dropdown(
194
+ choices=["Select a case..."] + list(get_case_titles().keys()),
195
+ label="Choose a Sample Case",
196
+ value="Select a case...",
197
+ interactive=True
198
+ )
199
+
200
+ # Custom case input
201
+ custom_case = gr.Textbox(
202
+ label="Or Input Custom Medical Case",
203
+ placeholder="Describe the patient's symptoms, history, and examination findings...",
204
+ lines=8,
205
+ interactive=True
206
+ )
207
+
208
+ # Analysis button
209
+ analyze_btn = gr.Button(
210
+ "🔍 Run Analysis",
211
+ variant="primary",
212
+ size="lg"
213
+ )
214
+
215
+ # Clear button
216
+ clear_btn = gr.Button(
217
+ "🗑️ Clear Analysis",
218
+ variant="secondary"
219
+ )
220
+
221
+ # Learning points button
222
+ learning_btn = gr.Button(
223
+ "📚 Show Learning Points",
224
+ variant="secondary"
225
+ )
226
+
227
+ with gr.Column(scale=2):
228
+ gr.Markdown("### 📊 Analysis Results")
229
+
230
+ # Case display
231
+ case_display = gr.Markdown(
232
+ label="Case Information",
233
+ elem_classes=["case-display"]
234
+ )
235
+
236
+ # Agent outputs
237
+ agent1_output = gr.Markdown(
238
+ label="Agent 1: Initial Diagnosis",
239
+ elem_classes=["agent-output"]
240
+ )
241
+
242
+ agent2_output = gr.Markdown(
243
+ label="Agent 2: Devil's Advocate Critique",
244
+ elem_classes=["agent-output"]
245
+ )
246
+
247
+ agent3_output = gr.Markdown(
248
+ label="Agent 3: Final Synthesis",
249
+ elem_classes=["agent-output"]
250
+ )
251
+
252
+ # Bias analysis
253
+ bias_analysis = gr.Markdown(
254
+ label="Expected Bias Analysis",
255
+ elem_classes=["bias-highlight"]
256
+ )
257
+
258
+ # Learning points
259
+ learning_points = gr.Markdown(
260
+ label="Learning Points",
261
+ visible=False
262
+ )
263
+
264
+ # Event handlers
265
+ analyze_btn.click(
266
+ fn=analyze_medical_case,
267
+ inputs=[case_dropdown, custom_case],
268
+ outputs=[case_display, agent1_output, agent2_output, agent3_output, bias_analysis]
269
+ )
270
+
271
+ clear_btn.click(
272
+ fn=clear_analysis,
273
+ outputs=[case_display, agent1_output, agent2_output, agent3_output, bias_analysis]
274
+ )
275
+
276
+ learning_btn.click(
277
+ fn=get_learning_points,
278
+ outputs=learning_points
279
+ )
280
+
281
+ # Show learning points when button is clicked
282
+ learning_btn.click(
283
+ fn=lambda: gr.update(visible=True),
284
+ outputs=learning_points
285
+ )
286
+
287
+ # Auto-select custom case when custom text is entered
288
+ def on_custom_text_change(text):
289
+ if text.strip():
290
+ return "custom"
291
+ return case_dropdown.value
292
+
293
+ custom_case.change(
294
+ fn=on_custom_text_change,
295
+ inputs=custom_case,
296
+ outputs=case_dropdown
297
+ )
298
+
299
+ gr.Markdown("""
300
+ ---
301
+ **Note**: This is a demonstration system for educational purposes.
302
+ The AI agents simulate medical reasoning but should not be used for actual clinical decision-making.
303
+ """)
304
+
305
+ return interface
306
 
307
  # Main execution
308
  if __name__ == "__main__":
309
+ # Create and launch the interface
310
+ interface = create_interface()
311
+ interface.launch(
312
+ server_name="0.0.0.0",
313
+ server_port=7860,
314
+ share=False,
315
+ show_error=True,
316
+ quiet=False
317
+ )