pinheirochagas commited on
Commit
955bf00
·
verified ·
1 Parent(s): f4bfbb2

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -1,12 +1,35 @@
1
  ---
2
- title: Citation Generator
3
- emoji: 🌍
4
- colorFrom: red
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.34.1
8
- app_file: gradio_ui_enhanced.py
9
  pinned: false
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Clinical Diagnosis Explorer
3
+ emoji: 🏥
4
+ colorFrom: blue
5
  colorTo: green
6
  sdk: gradio
7
  sdk_version: 5.34.1
8
+ app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # Clinical Diagnosis Explorer
13
+
14
+ Extract diagnostic claims and ground them in the scientific literature with PubMed search.
15
+
16
+ ## Features
17
+
18
+ - **Diagnosis-First Approach**: Automatically extracts primary diagnosis and related clinical claims
19
+ - **Automated PubMed Search**: Generates optimized search queries and retrieves relevant research
20
+ - **Evidence Analysis**: Uses GPT-4o to analyze research evidence and assess claim validity
21
+ - **Citation Integration**: Generates revised clinical notes with inline citations
22
+
23
+ ## Usage
24
+
25
+ 1. Enter a clinical diagnosis or clinical note
26
+ 2. Click "Extract Claims & Search Literature"
27
+ 3. Review the evidence-based analysis with citations
28
+
29
+ ## Example Input
30
+
31
+ ```
32
+ 28-year-old female with progressive word comprehension deficits, surface dyslexia, and difficulty naming objects while speech remains fluent. MRI reveals bilateral anterior temporal lobe atrophy. Diagnosis: Semantic Variant Primary Progressive Aphasia (svPPA).
33
+ ```
34
+
35
+
__pycache__/clinical_diag_exp_enhanced.cpython-39.pyc ADDED
Binary file (25.8 kB). View file
 
__pycache__/simple_pubmed.cpython-39.pyc ADDED
Binary file (27.9 kB). View file
 
app.py CHANGED
@@ -1,18 +1,246 @@
1
  import gradio as gr
2
- from anthropic import Anthropic
 
 
 
3
 
4
- def get_api_key(api_key):
5
- return f"API Key received: {api_key}"
6
 
7
- iface = gr.Interface(
8
- fn=get_api_key,
9
- inputs=gr.Textbox(label="Enter your Anthropic API key here:"),
10
- outputs=gr.Textbox(label="Response"),
11
- title="Anthropic API Key"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
 
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  if __name__ == "__main__":
18
- iface.launch(share=True)
 
 
1
  import gradio as gr
2
+ import os
3
+ import sys
4
+ from typing import Dict, Any
5
+ import json
6
 
7
+ # Import the ENHANCED clinical diagnosis exploration functions
8
+ from clinical_diag_exp_enhanced import run_clinical_diagnosis_exploration_enhanced
9
 
10
+ # Get API key from environment variable
11
+ try:
12
+ API_KEY = os.environ['OPENAI_API_KEY']
13
+ if not API_KEY:
14
+ raise KeyError("OPENAI_API_KEY is empty")
15
+ except KeyError:
16
+ print("ERROR: OPENAI_API_KEY environment variable not found or empty.")
17
+ print("Please set your OpenAI API key as an environment variable:")
18
+ print("export OPENAI_API_KEY='your-api-key-here'")
19
+ sys.exit(1)
20
+
21
+
22
+ def run_clinical_exploration_enhanced(diagnosis_text: str, max_results: int, max_claims: int) -> Dict[str, Any]:
23
+ """
24
+ Run the ENHANCED clinical diagnosis exploration pipeline.
25
+ """
26
+ if not diagnosis_text or diagnosis_text.strip() == "":
27
+ return {
28
+ "error": "Please enter a clinical diagnosis or note to analyze."
29
+ }
30
+
31
+ try:
32
+ # Run the ENHANCED clinical diagnosis exploration
33
+ results = run_clinical_diagnosis_exploration_enhanced(
34
+ diagnosis_text=diagnosis_text.strip(),
35
+ max_results_per_claim=max_results,
36
+ max_claims=max_claims
37
+ )
38
+
39
+ # Format the results for display
40
+ formatted_results = {
41
+ "diagnosis": results.get("diagnosis", "No diagnosis extracted"),
42
+ "revised_note": results.get("revised_note", "No revised note generated"),
43
+ "total_claims": results.get("claims_researched", 0),
44
+ "total_articles": results.get("total_articles", 0),
45
+ "total_llm_calls": results.get("total_llm_calls", 0),
46
+ "claims_summary": "",
47
+ "references": results.get("references", "No references available."),
48
+ "missing_information": results.get("missing_information", []),
49
+ "enhancement": results.get("enhancement", "Enhanced features"),
50
+ }
51
+
52
+ # Create a detailed summary of claims with full research summaries and references
53
+ claims = results.get("claims", [])
54
+ claims_summary = "## Claims Analyzed:\n\n"
55
+ for i, claim in enumerate(claims, 1):
56
+ claims_summary += f"### Claim {i}: {claim['claim_text']}\n"
57
+ claims_summary += f"- **Type:** {claim['claim_type']}\n"
58
+ claims_summary += f"- **Evidence Level:** {claim['evidence_level']}\n"
59
+ claims_summary += f"- **Research Status:** {claim['research_status']}\n"
60
+ claims_summary += f"- **Evidence Quality:** {claim.get('evidence_quality', 'unknown')}\n"
61
+ claims_summary += f"- **Articles Found:** {len(claim['abstracts'])}\n"
62
+ claims_summary += f"- **PubMed Query:** `{claim['pubmed_query']}`\n"
63
+ claims_summary += f"- **Query Strategy:** {claim.get('query_strategy', 'Standard query')}\n\n"
64
+
65
+ # Add the detailed summary with inline citations
66
+ claims_summary += f"**Detailed Summary:** {claim['detailed_summary']}\n\n"
67
+ claims_summary += f"**Brief Summary:** {claim['evidence_summary']}\n\n"
68
+
69
+ # Add critical assessment
70
+ if claim.get('critical_assessment'):
71
+ claims_summary += f"**Critical Assessment:** {claim['critical_assessment']}\n\n"
72
+
73
+ # Add supporting/contradicting evidence if available
74
+ if claim.get('supporting_evidence'):
75
+ claims_summary += "**Supporting Evidence:**\n"
76
+ for evidence in claim['supporting_evidence']:
77
+ claims_summary += f"- {evidence}\n"
78
+ claims_summary += "\n"
79
+
80
+ if claim.get('contradicting_evidence'):
81
+ claims_summary += "**Contradicting Evidence:**\n"
82
+ for evidence in claim['contradicting_evidence']:
83
+ claims_summary += f"- {evidence}\n"
84
+ claims_summary += "\n"
85
+
86
+ # Add methodological issues if available
87
+ if claim.get('methodological_issues'):
88
+ claims_summary += "**Methodological Issues:**\n"
89
+ for issue in claim['methodological_issues']:
90
+ claims_summary += f"- {issue}\n"
91
+ claims_summary += "\n"
92
+
93
+ # Add the claim-specific references
94
+ claims_summary += f"**Claim-Specific References:**\n{claim['claim_references']}\n\n"
95
+
96
+ claims_summary += "---\n\n"
97
+
98
+ formatted_results["claims_summary"] = claims_summary
99
+
100
+ return formatted_results
101
+
102
+ except Exception as e:
103
+ return {
104
+ "error": f"Error running clinical exploration: {str(e)}"
105
+ }
106
+
107
+ def create_interface():
108
+ """
109
+ Create the Clinical Diagnosis Explorer interface for extracting diagnostic claims
110
+ and grounding them in scientific literature with PubMed search.
111
+ """
112
 
113
+ with gr.Blocks(title="Clinical Diagnosis Explorer", theme=gr.themes.Soft()) as iface:
114
+
115
+ gr.Markdown("# Clinical Diagnosis Explorer")
116
+ gr.Markdown("**Extract diagnostic claims and ground them in the scientific literature with PubMed search**")
117
+ gr.Markdown("*Features: Diagnosis-first approach, fuzzy search, LLM analysis, evidence-based clinical notes*")
118
+
119
+ with gr.Tab("Clinical Analysis"):
120
+ gr.Markdown("## Analyze Clinical Diagnosis")
121
+ gr.Markdown("Enter a clinical diagnosis or clinical note to extract diagnostic claims and validate each against medical literature using PubMed search.")
122
+
123
+ diagnosis_input = gr.Textbox(
124
+ label="Clinical Diagnosis or Note",
125
+ placeholder="Enter the clinical diagnosis, clinical note, or diagnostic reasoning here...\n\nExample: 28-year-old right-handed female with an 18-month history of progressive word comprehension deficits, surface dyslexia, and difficulty naming common objects while speech remains fluent and grammatically intact. MRI reveals bilateral anterior temporal lobe atrophy. Diagnosis: Semantic Variant Primary Progressive Aphasia (svPPA).",
126
+ lines=12
127
+ )
128
+
129
+ with gr.Row():
130
+ max_results = gr.Slider(
131
+ minimum=5,
132
+ maximum=20,
133
+ value=12,
134
+ step=1,
135
+ label="Max Results per Claim",
136
+ info="Number of PubMed articles to analyze per claim (increased for better analysis)"
137
+ )
138
+
139
+ max_claims = gr.Slider(
140
+ minimum=3,
141
+ maximum=12,
142
+ value=8,
143
+ step=1,
144
+ label="Max Claims to Extract",
145
+ info="Maximum number of claims to extract from diagnosis"
146
+ )
147
+
148
+ analyze_btn = gr.Button("🔍 Extract Claims & Search Literature", variant="primary")
149
+
150
+ with gr.Row():
151
+ with gr.Column():
152
+ gr.Markdown("### Results")
153
+ results_output = gr.JSON(label="Analysis Results", visible=False)
154
+ error_output = gr.Textbox(label="Error", visible=False)
155
+
156
+ with gr.Column():
157
+ gr.Markdown("### Analysis Summary")
158
+ summary_output = gr.Markdown(label="Analysis Summary")
159
+
160
+ with gr.Row():
161
+ with gr.Column():
162
+ gr.Markdown("### Primary Diagnosis")
163
+ diagnosis_output = gr.Markdown(label="Extracted Diagnosis")
164
+
165
+ with gr.Column():
166
+ gr.Markdown("### Missing Information")
167
+ missing_info_output = gr.Markdown(label="Missing Information")
168
+
169
+ with gr.Row():
170
+ gr.Markdown("### Revised Clinical Note")
171
+ revised_note_output = gr.Markdown(label="Evidence-Based Clinical Note")
172
+
173
+ with gr.Row():
174
+ with gr.Column():
175
+ gr.Markdown("### Claims Analysis")
176
+ claims_output = gr.Markdown(label="Detailed Claims Analysis")
177
+
178
+ with gr.Column():
179
+ gr.Markdown("### References")
180
+ references_output = gr.Markdown(label="Research References")
181
+
182
+ def process_enhanced_analysis(diagnosis, max_res, max_clms):
183
+ results = run_clinical_exploration_enhanced(diagnosis, max_res, max_clms)
184
+
185
+ if "error" in results:
186
+ return {
187
+ results_output: gr.update(value=None, visible=False),
188
+ error_output: gr.update(value=results["error"], visible=True),
189
+ summary_output: gr.update(value=""),
190
+ diagnosis_output: gr.update(value=""),
191
+ missing_info_output: gr.update(value=""),
192
+ revised_note_output: gr.update(value=""),
193
+ claims_output: gr.update(value=""),
194
+ references_output: gr.update(value=""),
195
+ }
196
+ else:
197
+ # Format analysis summary
198
+ summary_text = f"""
199
+ ## Analysis Summary
200
+
201
+ **Total Claims Researched:** {results.get("total_claims", 0)}
202
+ **Total Articles Found:** {results.get("total_articles", 0)}
203
+ **Total LLM Calls:** {results.get("total_llm_calls", 0)}
204
+ **Enhanced Features:** {results.get("enhancement", "Enhanced analysis")}
205
+ """
206
+
207
+ # Format diagnosis
208
+ diagnosis_text = f"""
209
+ ## Primary Diagnosis
210
 
211
+ **Extracted Diagnosis:** {results.get("diagnosis", "No diagnosis extracted")}
212
 
213
+ This diagnosis was identified as the primary clinical condition and used as the foundation for claim extraction and PubMed query generation.
214
+ """
215
+
216
+ # Format missing information
217
+ missing_info = results.get("missing_information", [])
218
+ missing_text = "## Missing Information\n\n"
219
+ if missing_info:
220
+ for i, info in enumerate(missing_info, 1):
221
+ missing_text += f"{i}. ❓ {info}\n"
222
+ else:
223
+ missing_text += "No missing information identified."
224
+
225
+ return {
226
+ results_output: gr.update(value=results, visible=True),
227
+ error_output: gr.update(value="", visible=False),
228
+ summary_output: gr.update(value=summary_text),
229
+ diagnosis_output: gr.update(value=diagnosis_text),
230
+ missing_info_output: gr.update(value=missing_text),
231
+ revised_note_output: gr.update(value=results["revised_note"]),
232
+ claims_output: gr.update(value=results["claims_summary"]),
233
+ references_output: gr.update(value=results["references"]),
234
+ }
235
+
236
+ analyze_btn.click(
237
+ fn=process_enhanced_analysis,
238
+ inputs=[diagnosis_input, max_results, max_claims],
239
+ outputs=[results_output, error_output, summary_output, diagnosis_output, missing_info_output, revised_note_output, claims_output, references_output]
240
+ )
241
+
242
+ return iface
243
 
244
  if __name__ == "__main__":
245
+ iface = create_interface()
246
+ iface.launch()
clinical_diag_exp_enhanced.py CHANGED
@@ -9,6 +9,7 @@ import os
9
  from typing import List, Dict, Tuple
10
  import xml.etree.ElementTree as ET
11
  import re
 
12
 
13
  # Import functions from simple_pubmed.py
14
  from simple_pubmed import (
@@ -21,9 +22,18 @@ from simple_pubmed import (
21
  create_fallback_search
22
  )
23
 
24
- os.environ["OPENAI_API_KEY"] = ""
 
 
 
 
 
 
 
 
 
25
 
26
- client = OpenAI()
27
 
28
  #%%
29
  # ENHANCED: Diagnosis-first claim extraction with fuzzy search
@@ -139,7 +149,7 @@ def extract_clinical_claims_and_gram(diagnosis_text: str, max_claims: int = 12)
139
 
140
  try:
141
  response = client.chat.completions.create(
142
- model="gpt-4o", # ENHANCED: Use GPT-4o for better analysis
143
  messages=messages,
144
  tools=tools,
145
  tool_choice={"type": "function", "function": {"name": "create_diagnosis_and_claims"}}
@@ -181,7 +191,6 @@ def extract_clinical_claims_and_gram(diagnosis_text: str, max_claims: int = 12)
181
  def research_claim_enhanced(claim: Dict, max_results: int = 12) -> Dict:
182
  """
183
  ENHANCED: Research claim with more rigorous evidence evaluation and fuzzy search.
184
- Uses GPT-4o for better analysis and is willing to say "no" when evidence doesn't support claims.
185
 
186
  Args:
187
  claim (Dict): Claim with PubMed query
@@ -386,7 +395,6 @@ def fetch_pubmed_abstracts_direct(pmids: List[str]) -> List[Dict]:
386
 
387
  def generate_enhanced_summary_and_analysis(claim: Dict, abstracts: List[Dict]) -> Dict:
388
  """
389
- ENHANCED: Comprehensive analysis with GPT-4o for better rigor.
390
  Willing to say "no" when evidence doesn't support claims.
391
  """
392
 
@@ -501,7 +509,7 @@ def generate_enhanced_summary_and_analysis(claim: Dict, abstracts: List[Dict]) -
501
 
502
  try:
503
  response = client.chat.completions.create(
504
- model="gpt-4o", # ENHANCED: Use GPT-4o for better analysis
505
  messages=messages,
506
  tools=tools,
507
  tool_choice={"type": "function", "function": {"name": "create_enhanced_analysis"}}
@@ -659,7 +667,7 @@ def generate_revised_clinical_note(original_note: str, researched_claims: List[D
659
 
660
  try:
661
  response = client.chat.completions.create(
662
- model="gpt-4o", # ENHANCED: Use GPT-4o for better note revision
663
  messages=messages,
664
  tools=tools,
665
  tool_choice={"type": "function", "function": {"name": "create_revised_note"}}
@@ -686,7 +694,7 @@ def run_clinical_diagnosis_exploration_enhanced(diagnosis_text: str, max_results
686
  ENHANCEMENTS:
687
  1. Diagnosis-first claim extraction approach
688
  2. Fuzzy search with multiple fallback strategies
689
- 3. Enhanced evidence analysis with GPT-4o
690
  4. Willingness to say "no" when evidence doesn't support claims
691
  5. Revised clinical note output with citations and evidence
692
  6. Better cost-quality balance (increased cost for better results)
@@ -768,7 +776,7 @@ def run_clinical_diagnosis_exploration_enhanced(diagnosis_text: str, max_results
768
  "total_articles": len(unique_abstracts),
769
  "claims_researched": len(researched_claims),
770
  "missing_information": claims_analysis['missing_information'],
771
- "enhancement": "GPT-4o analysis, diagnosis-first approach, fuzzy search, revised note output"
772
  }
773
 
774
  print("=== ENHANCED PIPELINE COMPLETE ===")
@@ -776,7 +784,7 @@ def run_clinical_diagnosis_exploration_enhanced(diagnosis_text: str, max_results
776
  print(f"Claims researched: {results['claims_researched']}")
777
  print(f"Total articles: {results['total_articles']}")
778
  print(f"Missing information identified: {len(results['missing_information'])}")
779
- print(f"Enhanced features: Diagnosis-first approach, fuzzy search, revised note output, GPT-4o analysis")
780
  print()
781
 
782
  return results
 
9
  from typing import List, Dict, Tuple
10
  import xml.etree.ElementTree as ET
11
  import re
12
+ import sys
13
 
14
  # Import functions from simple_pubmed.py
15
  from simple_pubmed import (
 
22
  create_fallback_search
23
  )
24
 
25
+ # Get API key from environment variable
26
+ try:
27
+ API_KEY = os.environ['OPENAI_API_KEY']
28
+ if not API_KEY:
29
+ raise KeyError("OPENAI_API_KEY is empty")
30
+ except KeyError:
31
+ print("ERROR: OPENAI_API_KEY environment variable not found or empty.")
32
+ print("Please set your OpenAI API key as an environment variable:")
33
+ print("export OPENAI_API_KEY='your-api-key-here'")
34
+ sys.exit(1)
35
 
36
+ client = OpenAI(api_key=API_KEY)
37
 
38
  #%%
39
  # ENHANCED: Diagnosis-first claim extraction with fuzzy search
 
149
 
150
  try:
151
  response = client.chat.completions.create(
152
+ model="gpt-4.1",
153
  messages=messages,
154
  tools=tools,
155
  tool_choice={"type": "function", "function": {"name": "create_diagnosis_and_claims"}}
 
191
  def research_claim_enhanced(claim: Dict, max_results: int = 12) -> Dict:
192
  """
193
  ENHANCED: Research claim with more rigorous evidence evaluation and fuzzy search.
 
194
 
195
  Args:
196
  claim (Dict): Claim with PubMed query
 
395
 
396
  def generate_enhanced_summary_and_analysis(claim: Dict, abstracts: List[Dict]) -> Dict:
397
  """
 
398
  Willing to say "no" when evidence doesn't support claims.
399
  """
400
 
 
509
 
510
  try:
511
  response = client.chat.completions.create(
512
+ model="gpt-4.1", # ENHANCED: Use GPT-4o for better analysis
513
  messages=messages,
514
  tools=tools,
515
  tool_choice={"type": "function", "function": {"name": "create_enhanced_analysis"}}
 
667
 
668
  try:
669
  response = client.chat.completions.create(
670
+ model="gpt-4.1", # ENHANCED: Use GPT-4o for better note revision
671
  messages=messages,
672
  tools=tools,
673
  tool_choice={"type": "function", "function": {"name": "create_revised_note"}}
 
694
  ENHANCEMENTS:
695
  1. Diagnosis-first claim extraction approach
696
  2. Fuzzy search with multiple fallback strategies
697
+ 3. Enhanced evidence analysis
698
  4. Willingness to say "no" when evidence doesn't support claims
699
  5. Revised clinical note output with citations and evidence
700
  6. Better cost-quality balance (increased cost for better results)
 
776
  "total_articles": len(unique_abstracts),
777
  "claims_researched": len(researched_claims),
778
  "missing_information": claims_analysis['missing_information'],
779
+ "enhancement": "diagnosis-first approach, fuzzy search, revised note output"
780
  }
781
 
782
  print("=== ENHANCED PIPELINE COMPLETE ===")
 
784
  print(f"Claims researched: {results['claims_researched']}")
785
  print(f"Total articles: {results['total_articles']}")
786
  print(f"Missing information identified: {len(results['missing_information'])}")
787
+ print(f"Enhanced features: Diagnosis-first approach, fuzzy search, revised note output")
788
  print()
789
 
790
  return results
requirements.txt CHANGED
@@ -1,37 +1,18 @@
1
- # Core dependencies for clinical diagnosis exploration tools
2
  openai>=1.0.0
3
- anthropic>=0.8.0
4
 
5
  # Data processing and analysis
6
  pandas>=2.0.0
7
- numpy>=1.24.0
8
- scipy>=1.10.0
9
 
10
  # Web scraping and API interactions
11
  requests>=2.28.0
12
  beautifulsoup4>=4.11.0
13
 
14
- # Machine learning and statistical analysis
15
- scikit-learn>=1.2.0
16
- matplotlib>=3.7.0
17
- seaborn>=0.12.0
18
- statsmodels>=0.13.0
19
-
20
  # Web interface
21
  gradio>=4.0.0
22
 
23
- # Environment and configuration
24
- python-dotenv>=0.19.0
25
-
26
  # Type hints and utilities
27
  typing-extensions>=4.0.0
28
- pathlib>=1.0.1
29
-
30
- # JSON schema validation
31
- jsonschema>=4.17.3
32
 
33
- # Development and testing (optional)
34
- pytest>=6.0
35
- black>=22.0
36
- isort>=5.0
37
- mypy>=0.900
 
1
+ # Core dependencies for clinical diagnosis exploration
2
  openai>=1.0.0
 
3
 
4
  # Data processing and analysis
5
  pandas>=2.0.0
 
 
6
 
7
  # Web scraping and API interactions
8
  requests>=2.28.0
9
  beautifulsoup4>=4.11.0
10
 
 
 
 
 
 
 
11
  # Web interface
12
  gradio>=4.0.0
13
 
 
 
 
14
  # Type hints and utilities
15
  typing-extensions>=4.0.0
 
 
 
 
16
 
17
+ # JSON schema validation (for LLM function calling)
18
+ jsonschema>=4.17.3
 
 
 
simple_pubmed.py CHANGED
@@ -9,10 +9,20 @@ import os
9
  from typing import List, Dict
10
  import xml.etree.ElementTree as ET
11
  import re
 
12
 
13
- os.environ["OPENAI_API_KEY"] = ""
 
 
 
 
 
 
 
 
 
14
 
15
- client = OpenAI()
16
 
17
  # Helper function to safely parse JSON from LLM function calls
18
  def safe_json_loads(json_string: str) -> dict:
 
9
  from typing import List, Dict
10
  import xml.etree.ElementTree as ET
11
  import re
12
+ import sys
13
 
14
+ # Get API key from environment variable
15
+ try:
16
+ API_KEY = os.environ['OPENAI_API_KEY']
17
+ if not API_KEY:
18
+ raise KeyError("OPENAI_API_KEY is empty")
19
+ except KeyError:
20
+ print("ERROR: OPENAI_API_KEY environment variable not found or empty.")
21
+ print("Please set your OpenAI API key as an environment variable:")
22
+ print("export OPENAI_API_KEY='your-api-key-here'")
23
+ sys.exit(1)
24
 
25
+ client = OpenAI(api_key=API_KEY)
26
 
27
  # Helper function to safely parse JSON from LLM function calls
28
  def safe_json_loads(json_string: str) -> dict: