Commit
·
bceeb47
1
Parent(s):
4ae1b75
Publish app
Browse files- .gitattributes +1 -35
- .gitignore +8 -0
- Methodology.png +0 -0
- README.md +83 -13
- app.py +1294 -0
- requirements.txt +174 -0
.gitattributes
CHANGED
@@ -1,35 +1 @@
|
|
1 |
-
|
2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
1 |
+
*data/data.rar filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.venv/
|
2 |
+
.streamlit/
|
3 |
+
data/.gitattributes
|
4 |
+
data/abstract_embeddings.npy
|
5 |
+
data/faiss_index.index
|
6 |
+
data/data.rar
|
7 |
+
data/parte_205.csv
|
8 |
+
data/pmids.npy
|
Methodology.png
ADDED
![]() |
README.md
CHANGED
@@ -1,13 +1,83 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# 🩺 CER Demo: *Fact-Checking Biomedical Claims*
|
2 |
+
|
3 |
+
Welcome to the demo of the *CER (Combining Evidence and Reasoning)* system for fact-checking biomedical claims. This tool combines PubMed, one of the leading biomedical knowledge bases, with Large Language Models (LLMs) to verify the accuracy of claims, generate justifications, and provide reliable classifications.
|
4 |
+
|
5 |
+
## 🎥 Demo (or GIF)
|
6 |
+
[Watch our demo]() to see how CER supports biomedical fact-checking and enhances the transparency of scientific recommendations!
|
7 |
+
|
8 |
+
## 📊 Data Sources
|
9 |
+
We use the following data sources for training and evaluating the system:
|
10 |
+
|
11 |
+
- **[PubMed](https://pubmed.ncbi.nlm.nih.gov/)**: A biomedical database containing over 20 million abstracts.
|
12 |
+
- **HealthFC**: 750 biomedical claims curated by *Vladika et al. (2024)*.
|
13 |
+
- **BioASQ-7b**: 745 claims from the *BioASQ Challenge, Nentidis et al. (2020)*.
|
14 |
+
- **SciFact**: 1.4k expert-annotated scientific claims (*Wadden et al., 2020*).
|
15 |
+
|
16 |
+
## 🛠 Technologies Used
|
17 |
+
- **Python**: Core programming language.
|
18 |
+
- **FAISS Indexing**: For efficient retrieval of biomedical abstracts.
|
19 |
+
- [**Meta-Llama-3.1-405B-Instruct**](https://huggingface.co/hugging-quants/Meta-Llama-3.1-405B-Instruct-AWQ-INT4): Language model for generating justifications.
|
20 |
+
- **PubMedBERT**: Classifier for claim evaluation.
|
21 |
+
- **Streamlit**: For building an interactive user interface.
|
22 |
+
|
23 |
+
The system is designed to work on both lightweight setups (Intel i7 CPU, 16GB RAM) and advanced environments with GPUs (e.g., NVIDIA Tesla T4), supporting complex tasks on large datasets.
|
24 |
+
|
25 |
+
## 🔬 Methodological Workflow
|
26 |
+
CER follows a structured workflow in three main phases:
|
27 |
+
|
28 |
+
1. **Evidence Retrieval**: Relevant abstracts are extracted from PubMed using a BM25 retrieval engine.
|
29 |
+
2. **Justification Generation**: The LLM generates explanations based on the retrieved abstracts.
|
30 |
+
3. **Claim Classification**: The classifier evaluates each claim as true, false, or "not enough evidence."
|
31 |
+
|
32 |
+

|
33 |
+
|
34 |
+
## 🌟 Key Features
|
35 |
+
- **Zero-Shot and Fine-Tuned Classification**: Provides reliable fact-checking without the need for extensive task-specific labeled data.
|
36 |
+
- **Robustness Across Datasets**: Fine-tuning enhances model performance, even when the training and test sets differ.
|
37 |
+
- **Efficient Retrieval**: Leverages the Sparse Retriever for quick and accurate evidence extraction from PubMed.
|
38 |
+
- **Transparency**: Generates justifications to explain the classification of each claim, ensuring transparency and interpretability.
|
39 |
+
|
40 |
+
## 🚀 Getting Started
|
41 |
+
Follow these steps to use the CER system demo:
|
42 |
+
|
43 |
+
### Prerequisites
|
44 |
+
- **Python 3.9+**
|
45 |
+
- Required libraries: Install with the command:
|
46 |
+
```bash
|
47 |
+
pip install -r requirements.txt
|
48 |
+
```
|
49 |
+
|
50 |
+
### Running the Application
|
51 |
+
1. **Clone the repository**:
|
52 |
+
```bash
|
53 |
+
git clone https://github.com/picuslab/CER-Fact-Checking.git
|
54 |
+
cd CER-Fact-Checking
|
55 |
+
```
|
56 |
+
2. **Create a virtual environment**:
|
57 |
+
```bash
|
58 |
+
python -m venv venv
|
59 |
+
source venv/bin/activate # On Windows use `venv\Scripts\activate`
|
60 |
+
```
|
61 |
+
3. **Run the Streamlit application**:
|
62 |
+
```bash
|
63 |
+
streamlit run app.py
|
64 |
+
```
|
65 |
+
Open your browser and go to `http://localhost:8501` to interact with the application.
|
66 |
+
|
67 |
+
### Submitting Claims
|
68 |
+
Enter a biomedical claim, for example:
|
69 |
+
```
|
70 |
+
"Vitamin D reduces the risk of osteoporosis."
|
71 |
+
```
|
72 |
+
Observe the process of evidence retrieval, justification generation, and classification.
|
73 |
+
|
74 |
+
## 📈 Conclusions
|
75 |
+
CER demonstrates how fact-checking using LLMs and evidence retrieval techniques can improve the reliability of medical information. Fine-tuning LLMs proves to be a powerful strategy for enhancing accuracy in fact-checking, even across different datasets. The ability to separate prediction from explanation ensures transparency and reduces bias.
|
76 |
+
|
77 |
+
## ⚖ Ethical Considerations
|
78 |
+
**CER** is a decision-support tool, not a substitute for professional medical advice. All recommendations must be validated by authorized healthcare providers. This demo uses anonymized data for illustrative purposes.
|
79 |
+
|
80 |
+
## 🙏 Acknowledgments
|
81 |
+
Special thanks to the dataset creators, library developers, and the research team for their contributions to this project.
|
82 |
+
|
83 |
+
👨💻 This project was developed by Mariano Barone, Antonio Romano, Giuseppe Riccio, Marco Postiglione, and Vincenzo Moscato.
|
app.py
ADDED
@@ -0,0 +1,1294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
import os
|
3 |
+
import faiss
|
4 |
+
import whisper
|
5 |
+
import ffmpeg
|
6 |
+
import tempfile
|
7 |
+
import requests
|
8 |
+
import numpy as np
|
9 |
+
import pandas as pd
|
10 |
+
import streamlit as st
|
11 |
+
|
12 |
+
from openai import OpenAI
|
13 |
+
from transformers import pipeline
|
14 |
+
from sentence_transformers import SentenceTransformer
|
15 |
+
from newsplease import NewsPlease
|
16 |
+
from streamlit_echarts import st_echarts
|
17 |
+
from streamlit_option_menu import option_menu
|
18 |
+
|
19 |
+
# NEWS to check
|
20 |
+
# https://fbe.unimelb.edu.au/newsroom/fake-news-in-the-age-of-covid-19 True Claim
|
21 |
+
# https://newssalutebenessere.altervista.org/covid-19-just-a-simple-flue-or-something-else/ False Claim
|
22 |
+
|
23 |
+
###### CONFIGURATIONS ######
|
24 |
+
# Debug mode
|
25 |
+
debug = False
|
26 |
+
|
27 |
+
# File paths
|
28 |
+
embeddings_file = r"data\abstract_embeddings.npy"
|
29 |
+
pmid_file = r"data\pmids.npy"
|
30 |
+
faiss_index_file = r"data\faiss_index.index"
|
31 |
+
file_path = r'data\parte_205.csv'
|
32 |
+
|
33 |
+
# Initialize OpenAI API client
|
34 |
+
client = OpenAI(
|
35 |
+
base_url="https://integrate.api.nvidia.com/v1",
|
36 |
+
api_key=st.secrets.nvidia.api_key
|
37 |
+
)
|
38 |
+
|
39 |
+
# Load data
|
40 |
+
data = pd.read_csv(file_path)
|
41 |
+
|
42 |
+
# Load the model
|
43 |
+
model = SentenceTransformer('all-MiniLM-L6-v2')
|
44 |
+
|
45 |
+
|
46 |
+
def get_article_data(url):
|
47 |
+
"""
|
48 |
+
Extracts article data from a specified URL.
|
49 |
+
|
50 |
+
Args:
|
51 |
+
url (str): URL of the article to analyze.
|
52 |
+
|
53 |
+
Returns:
|
54 |
+
dict: Structured article data, including: title, authors, publication date, and content.
|
55 |
+
"""
|
56 |
+
try:
|
57 |
+
# Make an HTTP request to the specified URL
|
58 |
+
response = requests.get(url)
|
59 |
+
# Check if the request was successful (i.e., status code 200)
|
60 |
+
response.raise_for_status()
|
61 |
+
|
62 |
+
# Extract the HTML content from the response
|
63 |
+
html_content = response.text
|
64 |
+
|
65 |
+
# Use NewsPlease to extract structured data from the HTML content
|
66 |
+
article = NewsPlease.from_html(html_content, url=url)
|
67 |
+
|
68 |
+
# Return the structured article data
|
69 |
+
return {
|
70 |
+
"title": article.title,
|
71 |
+
"authors": article.authors,
|
72 |
+
"date_publish": article.date_publish,
|
73 |
+
"content": article.maintext,
|
74 |
+
}
|
75 |
+
|
76 |
+
except requests.exceptions.RequestException as e:
|
77 |
+
return {"error": f"Error during URL retrieval: {e}"}
|
78 |
+
|
79 |
+
except Exception as e:
|
80 |
+
return {"error": f"Error processing the article: {e}"}
|
81 |
+
|
82 |
+
|
83 |
+
def extract_and_split_claims(claims):
|
84 |
+
"""
|
85 |
+
Extracts and splits claims from a given string.
|
86 |
+
|
87 |
+
Args:
|
88 |
+
claims (str): String containing claims.
|
89 |
+
|
90 |
+
Returns:
|
91 |
+
dict: Dictionary containing the extracted claims.
|
92 |
+
"""
|
93 |
+
start_index = claims.find("Claim 1:")
|
94 |
+
if start_index != -1:
|
95 |
+
claims = claims[start_index:]
|
96 |
+
|
97 |
+
claim_lines = claims.strip().split("\n\n")
|
98 |
+
|
99 |
+
claims_dict = {}
|
100 |
+
for i, claim in enumerate(claim_lines, start=1):
|
101 |
+
claims_dict[f"Claim_{i}"] = claim
|
102 |
+
|
103 |
+
for var_name, claim_text in claims_dict.items():
|
104 |
+
globals()[var_name] = claim_text
|
105 |
+
|
106 |
+
return claims_dict
|
107 |
+
|
108 |
+
|
109 |
+
def extract_label_and_score(result):
|
110 |
+
"""
|
111 |
+
Extracts the predicted label and score from the result string.
|
112 |
+
|
113 |
+
Args:
|
114 |
+
result (str): String containing the prediction result.
|
115 |
+
|
116 |
+
Returns:
|
117 |
+
tuple: Predicted label and score.
|
118 |
+
"""
|
119 |
+
# Extract the predicted label
|
120 |
+
label_match = re.search(r"'labels': \['(.*?)'", result)
|
121 |
+
predicted_label = label_match.group(1) if label_match else None
|
122 |
+
|
123 |
+
# Extract the score
|
124 |
+
score_match = re.search(r"'scores': \[(\d+\.\d+)", result)
|
125 |
+
score_label = float(score_match.group(1)) if score_match else None
|
126 |
+
|
127 |
+
return predicted_label, score_label
|
128 |
+
|
129 |
+
|
130 |
+
def clean_phrases(phrases, pattern):
|
131 |
+
"""
|
132 |
+
Clean and extract phrases from a list of strings using a specified pattern.
|
133 |
+
|
134 |
+
Args:
|
135 |
+
phrases (list): List of strings containing phrases.
|
136 |
+
pattern (str): Regular expression pattern to extract phrases.
|
137 |
+
|
138 |
+
Returns:
|
139 |
+
list: List of cleaned phrases as dictionaries with text and abstract keys
|
140 |
+
"""
|
141 |
+
cleaned_phrases = []
|
142 |
+
|
143 |
+
for phrase in phrases:
|
144 |
+
matches = re.findall(pattern, phrase)
|
145 |
+
cleaned_phrases.extend([{"text": match[0], "abstract": f"abstract_{match[1]}"} for match in matches])
|
146 |
+
|
147 |
+
return cleaned_phrases
|
148 |
+
|
149 |
+
|
150 |
+
def highlight_phrases(abstract_text, phrases, color, label):
|
151 |
+
"""
|
152 |
+
Highlight phrases in the abstract text with the specified background color.
|
153 |
+
|
154 |
+
Args:
|
155 |
+
abstract_text (str): Text of the abstract to highlight.
|
156 |
+
phrases (list): List of phrases to highlight.
|
157 |
+
color (str): Background color to use for highlighting.
|
158 |
+
label (str): Predicted label for the claim.
|
159 |
+
|
160 |
+
Returns:
|
161 |
+
str: Abstract text with highlighted phrases.
|
162 |
+
"""
|
163 |
+
# Switch colors if the label is "False"
|
164 |
+
if label.lower() == "false":
|
165 |
+
color = "lightgreen" if color == "red" else color
|
166 |
+
|
167 |
+
# Highlight each phrase in the abstract text
|
168 |
+
for phrase in phrases:
|
169 |
+
abstract_text = re.sub(
|
170 |
+
re.escape(phrase["text"]),
|
171 |
+
f'<span style="background-color: {color}; font-weight: bold; border: 1px solid black; border-radius: 5px;">{phrase["text"]}</span>',
|
172 |
+
abstract_text,
|
173 |
+
flags=re.IGNORECASE
|
174 |
+
)
|
175 |
+
|
176 |
+
return abstract_text
|
177 |
+
|
178 |
+
|
179 |
+
def parse_response(response):
|
180 |
+
"""
|
181 |
+
Parse the response from the model and extract the fields.
|
182 |
+
|
183 |
+
Args:
|
184 |
+
response (str): Response string from the model.
|
185 |
+
|
186 |
+
Returns:
|
187 |
+
tuple: Extracted fields from the response.
|
188 |
+
"""
|
189 |
+
# Initial values for the fields
|
190 |
+
first_label = "Non trovato"
|
191 |
+
justification = "Non trovato"
|
192 |
+
supporting = "Non trovato"
|
193 |
+
refusing = "Non trovato"
|
194 |
+
notes = "Non trovato"
|
195 |
+
|
196 |
+
# Regular expression patterns for extracting fields
|
197 |
+
patterns = {
|
198 |
+
"first_label": r"Label:\s*(.*?)\n",
|
199 |
+
"justification": r"Justification:\s*(.*?)(?=\nSupporting sentences)",
|
200 |
+
"supporting": r"Supporting sentences from abstracts:\n(.*?)(?=\nRefusing sentences)",
|
201 |
+
"refusing": r"Refusing sentences from abstracts:\n(.*?)(?=\nNote:)",
|
202 |
+
"notes": r"Note:\s*(.*)"
|
203 |
+
}
|
204 |
+
|
205 |
+
# Extract the fields using regular expressions
|
206 |
+
if match := re.search(patterns["first_label"], response, re.DOTALL):
|
207 |
+
first_label = match.group(1).strip()
|
208 |
+
if match := re.search(patterns["justification"], response, re.DOTALL):
|
209 |
+
justification = match.group(1).strip()
|
210 |
+
if match := re.search(patterns["supporting"], response, re.DOTALL):
|
211 |
+
supporting = [{"text": sentence.strip(), "abstract": f"abstract_{i+1}"} for i, sentence in enumerate(match.group(1).strip().split('\n'))]
|
212 |
+
if match := re.search(patterns["refusing"], response, re.DOTALL):
|
213 |
+
refusing = [{"text": sentence.strip(), "abstract": f"abstract_{i+1}"} for i, sentence in enumerate(match.group(1).strip().split('\n'))]
|
214 |
+
if match := re.search(patterns["notes"], response, re.DOTALL):
|
215 |
+
notes = match.group(1).strip()
|
216 |
+
|
217 |
+
# Return the extracted fields
|
218 |
+
return first_label, justification, supporting, refusing, notes
|
219 |
+
|
220 |
+
|
221 |
+
def load_embeddings(embeddings_file, pmid_file, faiss_index_file, debug=False):
|
222 |
+
"""
|
223 |
+
Load embeddings, PMIDs, and FAISS index from the specified files.
|
224 |
+
|
225 |
+
Args:
|
226 |
+
embeddings_file (str): File path for the embeddings.
|
227 |
+
pmid_file (str): File path for the PMIDs.
|
228 |
+
faiss_index_file (str): File path for the FAISS index.
|
229 |
+
|
230 |
+
Returns:
|
231 |
+
tuple: Tuple containing the embeddings, PMIDs, and FAISS index.
|
232 |
+
"""
|
233 |
+
# Check if the files exist
|
234 |
+
if not (os.path.exists(embeddings_file) and os.path.exists(pmid_file) and os.path.exists(faiss_index_file)):
|
235 |
+
raise FileNotFoundError("One or more files not found. Please check the file paths.")
|
236 |
+
|
237 |
+
# Load the embeddings and PMIDs
|
238 |
+
embeddings = np.load(embeddings_file)
|
239 |
+
pmids = np.load(pmid_file, allow_pickle=True)
|
240 |
+
|
241 |
+
# Load the FAISS index
|
242 |
+
index = faiss.read_index(faiss_index_file)
|
243 |
+
|
244 |
+
if debug:
|
245 |
+
print("Embeddings, PMIDs, and FAISS index loaded successfully.")
|
246 |
+
|
247 |
+
return embeddings, pmids, index
|
248 |
+
|
249 |
+
|
250 |
+
def retrieve_top_abstracts(claim, model, index, pmids, data, top_k=5):
|
251 |
+
"""
|
252 |
+
Retrieve the top abstracts from the FAISS index for a given claim.
|
253 |
+
|
254 |
+
Args:
|
255 |
+
claim (str): Claim to fact-check.
|
256 |
+
model (SentenceTransformer): Sentence transformer model for encoding text.
|
257 |
+
index (faiss.IndexFlatIP): FAISS index for similarity search.
|
258 |
+
pmids (np.ndarray): Array of PMIDs for the abstracts.
|
259 |
+
data (pd.DataFrame): DataFrame containing the abstract data.
|
260 |
+
top_k (int): Number of top abstracts to retrieve.
|
261 |
+
|
262 |
+
Returns:
|
263 |
+
list: List of tuples containing the abstract text, PMID, and distance.
|
264 |
+
"""
|
265 |
+
# Encode the claim using the SentenceTransformer model
|
266 |
+
claim_embedding = model.encode([claim])
|
267 |
+
faiss.normalize_L2(claim_embedding) # Normalize the claim embedding (with L2 norm)
|
268 |
+
distances, indices = index.search(claim_embedding, top_k)
|
269 |
+
|
270 |
+
# Retrieve the top abstracts based on the indices
|
271 |
+
results = []
|
272 |
+
for j, i in enumerate(indices[0]):
|
273 |
+
pmid = pmids[i]
|
274 |
+
abstract_text = data[data['PMID'] == pmid]['AbstractText'].values[0]
|
275 |
+
distance = distances[0][j]
|
276 |
+
results.append((abstract_text, pmid, distance))
|
277 |
+
|
278 |
+
return results
|
279 |
+
|
280 |
+
|
281 |
+
def generate_justification(query, justification):
|
282 |
+
"""
|
283 |
+
Generate a justification for the claim using the Zero-Shot Classification model.
|
284 |
+
|
285 |
+
Args:
|
286 |
+
query (str): Claim to fact-check.
|
287 |
+
justification (str): Justification for the claim.
|
288 |
+
|
289 |
+
Returns:
|
290 |
+
str: Final justification for the claim.
|
291 |
+
"""
|
292 |
+
# Define the classes for the Zero-Shot Classification model
|
293 |
+
Class = ["True", "False","NEI"]
|
294 |
+
|
295 |
+
# Generate the justification text
|
296 |
+
justification_text = (
|
297 |
+
f'Justification: "{justification}"'
|
298 |
+
)
|
299 |
+
|
300 |
+
# Limit the justification text to a maximum length
|
301 |
+
max_length = 512
|
302 |
+
if len(justification_text) > max_length:
|
303 |
+
justification_text = justification_text[:max_length]
|
304 |
+
|
305 |
+
# Generate the final justification using the Zero-Shot Classification model
|
306 |
+
output = zeroshot_classifier(
|
307 |
+
query,
|
308 |
+
Class,
|
309 |
+
hypothesis_template=f"The claim is '{{}}' for: {justification_text}",
|
310 |
+
multi_label=False
|
311 |
+
)
|
312 |
+
|
313 |
+
# Prepare the final justification text
|
314 |
+
final_justification = f'{output}.'
|
315 |
+
|
316 |
+
return final_justification
|
317 |
+
|
318 |
+
|
319 |
+
def llm_reasoning_template(query):
|
320 |
+
"""
|
321 |
+
Generate a template for the prompt used for justification generation by the LLM model.
|
322 |
+
|
323 |
+
Args:
|
324 |
+
query (str): Claim to fact-check.
|
325 |
+
|
326 |
+
Returns:
|
327 |
+
str: Reasoning template for the claim.
|
328 |
+
"""
|
329 |
+
llm_reasoning_prompt = f"""<<SYS>> [INST]
|
330 |
+
|
331 |
+
You are a helpful, respectful and honest Doctor. Always answer as helpfully as possible using the context text provided.
|
332 |
+
|
333 |
+
Use the information in Context.
|
334 |
+
|
335 |
+
Elaborate the Context to generate a new information.
|
336 |
+
|
337 |
+
Use only the knowledge in Context to answer.
|
338 |
+
|
339 |
+
Answer describing in a scentific way. Be formal during the answer. Use the third person.
|
340 |
+
|
341 |
+
Answer without mentioning the Context. Use it but don't refer to it in the text.
|
342 |
+
|
343 |
+
To answer, use max 300 word.
|
344 |
+
|
345 |
+
Create a Justification from the sentences given.
|
346 |
+
|
347 |
+
Use the structure: Justification: The claim is (label) because... (don't use the word "context")
|
348 |
+
|
349 |
+
Write as an online doctor to create the Justification.
|
350 |
+
|
351 |
+
After, give some sentences from Context from scientific papers: that supports the label and reject the label.
|
352 |
+
|
353 |
+
Supporting sentences from abstracts:
|
354 |
+
information sentence from abstract_1:
|
355 |
+
information sentence from abstract_2:
|
356 |
+
..
|
357 |
+
Refusing sentences from abstracts:
|
358 |
+
information sentence from abstract_1:
|
359 |
+
information sentence from abstract_2:
|
360 |
+
..
|
361 |
+
Add where it comes from (abstract_1, abstract_2, abstract_3, abstract_4, abstract_5)
|
362 |
+
|
363 |
+
With the answer, gives a line like: "Label:". Always put Label as first. After Label, give the Justification.
|
364 |
+
The justification will be always given as Justification:
|
365 |
+
Label can be yes, no, NEI, where yes: claim is true. no: claim is false. NEI: not enough information.
|
366 |
+
The Label will be chosen with a voting system of support/refuse before.
|
367 |
+
|
368 |
+
[/INST] <</SYS>>
|
369 |
+
|
370 |
+
[INST] Question: {query} [/INST]
|
371 |
+
[INST] Context from scientific papers:
|
372 |
+
"""
|
373 |
+
|
374 |
+
return llm_reasoning_prompt
|
375 |
+
|
376 |
+
|
377 |
+
def claim_detection_template(full_text):
|
378 |
+
"""
|
379 |
+
Generate a template for the prompt used for claim detection by the LLM model.
|
380 |
+
|
381 |
+
Args:
|
382 |
+
full_text (str): Full text to analyze.
|
383 |
+
|
384 |
+
Returns:
|
385 |
+
str: Template for claim detection.
|
386 |
+
"""
|
387 |
+
claim_detection_prompt = f"""<<SYS>> [INST]
|
388 |
+
|
389 |
+
Your task is to extract from the text potential health related question to verify their veracity.
|
390 |
+
|
391 |
+
The context extracted from the online where to take the claim is: {full_text}
|
392 |
+
|
393 |
+
Create simple claim of single sentence from the context.
|
394 |
+
|
395 |
+
Dont's use *
|
396 |
+
|
397 |
+
Give just the claim. Don't write other things.
|
398 |
+
|
399 |
+
Extract only health related claim.
|
400 |
+
|
401 |
+
Rank eventual claim like:
|
402 |
+
|
403 |
+
Claim 1:
|
404 |
+
Claim 2:
|
405 |
+
Claim 3:
|
406 |
+
|
407 |
+
Use always this structure.
|
408 |
+
Start every claim with "Claim " followed by the number.
|
409 |
+
|
410 |
+
The number of claims may go from 1 to a max of 5.
|
411 |
+
|
412 |
+
The claims have to be always health related. [/INST] <</SYS>>
|
413 |
+
"""
|
414 |
+
|
415 |
+
return claim_detection_prompt
|
416 |
+
|
417 |
+
|
418 |
+
# Page and Title Configuration
|
419 |
+
st.set_page_config(page_title="CER - Combining Evidence and Reasoning Demo", layout="wide", initial_sidebar_state="collapsed")
|
420 |
+
st.markdown("<h1 style='text-align: center; color: inherit;'>✔️✨ CER - Biomedical Fact Checker</h1>", unsafe_allow_html=True)
|
421 |
+
|
422 |
+
# Horizontal option menu for selecting the page
|
423 |
+
page = option_menu(None, ["Single claim check", "Page check", "Video check"],
|
424 |
+
icons=['check', 'ui-checks'],
|
425 |
+
menu_icon="cast", default_index=0, orientation="horizontal")
|
426 |
+
|
427 |
+
# Sidebar Configuration
|
428 |
+
st.sidebar.title("🔬 Combining Evidence and Reasoning Demo")
|
429 |
+
st.sidebar.caption("🔍 Fact-check biomedical claims using scientific evidence and reasoning.")
|
430 |
+
st.sidebar.markdown("---")
|
431 |
+
st.sidebar.caption("#### ℹ️ About")
|
432 |
+
st.sidebar.caption("This is a demo application for fact-checking biomedical claims using scientific evidence and reasoning. It uses a combination of language models, scientific literature, and reasoning to provide explanations for the predictions.")
|
433 |
+
|
434 |
+
# Load embeddings, PMIDs, and FAISS index
|
435 |
+
if 'embeddings_loaded' not in st.session_state:
|
436 |
+
embeddings, pmids, index = load_embeddings(embeddings_file, pmid_file, faiss_index_file, debug)
|
437 |
+
st.session_state.embeddings = embeddings
|
438 |
+
st.session_state.pmids = pmids
|
439 |
+
st.session_state.index = index
|
440 |
+
st.session_state.embeddings_loaded = True
|
441 |
+
else:
|
442 |
+
embeddings = st.session_state.embeddings
|
443 |
+
pmids = st.session_state.pmids
|
444 |
+
index = st.session_state.index
|
445 |
+
|
446 |
+
# Check if the claim and top_abstracts are in the session state
|
447 |
+
if 'claim' not in st.session_state:
|
448 |
+
st.session_state.claim = ""
|
449 |
+
|
450 |
+
if 'top_abstracts' not in st.session_state:
|
451 |
+
st.session_state.top_abstracts = []
|
452 |
+
|
453 |
+
|
454 |
+
#### Single claim check PAGE ####
|
455 |
+
if page == "Single claim check":
|
456 |
+
st.subheader("Single claim check")
|
457 |
+
st.caption("✨ Enter a single claim to fact-check and hit the button to see the results! 🔍")
|
458 |
+
|
459 |
+
st.session_state.claim = st.text_input("Claim to fact-check:")
|
460 |
+
|
461 |
+
if st.button("✨ Fact Check"):
|
462 |
+
|
463 |
+
if st.session_state.claim:
|
464 |
+
# Retrieve the top abstracts for the claim
|
465 |
+
top_abstracts = retrieve_top_abstracts(st.session_state.claim, model, index, pmids, data, top_k=5)
|
466 |
+
st.session_state.top_abstracts = top_abstracts
|
467 |
+
|
468 |
+
st.markdown("### **Results**")
|
469 |
+
|
470 |
+
with st.container():
|
471 |
+
for i, (abstract, pmid, distance) in enumerate(st.session_state.top_abstracts, 1):
|
472 |
+
pubmed_url = f"https://pubmed.ncbi.nlm.nih.gov/{pmid}/"
|
473 |
+
globals()[f"abstract_{i}"] = abstract
|
474 |
+
globals()[f"reference_{i}"] = pubmed_url
|
475 |
+
globals()[f"distance_{i}"] = distance
|
476 |
+
|
477 |
+
with st.spinner('🔍 We are checking...'):
|
478 |
+
try:
|
479 |
+
# Retrieve the question from the DataFrame
|
480 |
+
query = st.session_state.claim
|
481 |
+
|
482 |
+
# Generate the reasoning template
|
483 |
+
prompt_template = llm_reasoning_template(query)
|
484 |
+
|
485 |
+
# Add the abstracts to the prompt
|
486 |
+
for i in range(1, len(st.session_state.top_abstracts)):
|
487 |
+
prompt_template += f"{globals()[f'abstract_{i}']} ; "
|
488 |
+
prompt_template += f"{globals()[f'abstract_{i+1}']} [/INST]"
|
489 |
+
|
490 |
+
# Call the API
|
491 |
+
completion = client.chat.completions.create(
|
492 |
+
model="meta/llama-3.1-405b-instruct",
|
493 |
+
messages=[{"role": "user", "content": prompt_template}],
|
494 |
+
temperature=0.1,
|
495 |
+
top_p=0.7,
|
496 |
+
max_tokens=1024,
|
497 |
+
stream=True
|
498 |
+
)
|
499 |
+
|
500 |
+
# Collect the response
|
501 |
+
answer = ""
|
502 |
+
for chunk in completion:
|
503 |
+
if chunk.choices[0].delta.content:
|
504 |
+
answer += chunk.choices[0].delta.content
|
505 |
+
|
506 |
+
# Debug: Check the answer
|
507 |
+
if debug:
|
508 |
+
print(f"{answer}")
|
509 |
+
|
510 |
+
except Exception as e:
|
511 |
+
st.write(f"Error processing index: {e}")
|
512 |
+
|
513 |
+
with st.spinner('🤔💬 Justifying the check...'):
|
514 |
+
# Perform parsing and separate variables
|
515 |
+
zeroshot_classifier = pipeline(
|
516 |
+
"zero-shot-classification", model="MoritzLaurer/deberta-v3-large-zeroshot-v1.1-all-33"
|
517 |
+
)
|
518 |
+
first_label, justification, supporting, refusing, notes = parse_response(answer)
|
519 |
+
|
520 |
+
with st.spinner('🕵️♂️📜 We are finding evidence...'):
|
521 |
+
# Generate the justification for the claim
|
522 |
+
result = generate_justification(st.session_state.claim, justification)
|
523 |
+
predicted_label, score_label = extract_label_and_score(result)
|
524 |
+
|
525 |
+
if predicted_label == "True":
|
526 |
+
color = f"rgba(0, 204, 0, {score_label})" # Green
|
527 |
+
elif predicted_label == "False":
|
528 |
+
color = f"rgba(204, 0, 0, {score_label})" # Red
|
529 |
+
elif predicted_label == "NEI":
|
530 |
+
color = f"rgba(255, 255, 0, {score_label})" # Yellow
|
531 |
+
else:
|
532 |
+
color = "black" # Default color
|
533 |
+
|
534 |
+
# Calculate the confidence score
|
535 |
+
confidence = f"{score_label * 100:.2f}%"
|
536 |
+
st.caption(f"📝 The Claim: {st.session_state.claim}")
|
537 |
+
st.markdown(
|
538 |
+
f"**Prediction of claim:** Most likely <span style='color: {color}; font-weight: bold;'>{predicted_label}</span> with a confidence of <span style='color: {color}; font-weight: bold;'>{confidence}</span>",
|
539 |
+
unsafe_allow_html=True
|
540 |
+
)
|
541 |
+
st.markdown("### **Justification**")
|
542 |
+
st.markdown(f'<p> {justification}</p>', unsafe_allow_html=True)
|
543 |
+
|
544 |
+
# Extract the abstracts and references
|
545 |
+
abstracts = {}
|
546 |
+
for i in range(1, len(st.session_state.top_abstracts) + 1):
|
547 |
+
abstracts[f"abstract_{i}"] = globals()[f"abstract_{i}"]
|
548 |
+
|
549 |
+
pattern = r'"\s*(.*?)\s*"\s*\(abstract_(\d+)\)'
|
550 |
+
|
551 |
+
supporting_texts = []
|
552 |
+
for item in supporting:
|
553 |
+
try:
|
554 |
+
supporting_texts.append(item["text"])
|
555 |
+
except (TypeError, KeyError):
|
556 |
+
continue
|
557 |
+
supporting = clean_phrases(supporting_texts, pattern)
|
558 |
+
|
559 |
+
refusing_text = []
|
560 |
+
for item in refusing:
|
561 |
+
try:
|
562 |
+
refusing_text.append(item["text"])
|
563 |
+
except (TypeError, KeyError):
|
564 |
+
continue
|
565 |
+
refusing = clean_phrases(refusing_text, pattern)
|
566 |
+
|
567 |
+
if debug:
|
568 |
+
print(supporting)
|
569 |
+
print(refusing)
|
570 |
+
|
571 |
+
processed_abstracts = {}
|
572 |
+
for abstract_name, abstract_text in abstracts.items():
|
573 |
+
# Highlight supporting phrases in green
|
574 |
+
supporting_matches = [phrase for phrase in supporting if phrase["abstract"] == abstract_name]
|
575 |
+
abstract_text = highlight_phrases(abstract_text, supporting_matches, "lightgreen", predicted_label)
|
576 |
+
|
577 |
+
# Highlight refusing phrases in red
|
578 |
+
refusing_matches = [phrase for phrase in refusing if phrase["abstract"] == abstract_name]
|
579 |
+
abstract_text = highlight_phrases(abstract_text, refusing_matches, "red", predicted_label)
|
580 |
+
|
581 |
+
# Add only if supporting matches are found
|
582 |
+
if supporting_matches:
|
583 |
+
# Add the reference if a corresponding variable exists
|
584 |
+
reference_variable = f"reference_{abstract_name.split('_')[1]}"
|
585 |
+
if reference_variable in globals():
|
586 |
+
reference_value = globals()[reference_variable]
|
587 |
+
abstract_text += f"<br><br><strong>🔗 Reference:</strong> {reference_value}"
|
588 |
+
|
589 |
+
# Add the processed abstract
|
590 |
+
processed_abstracts[abstract_name] = abstract_text
|
591 |
+
|
592 |
+
# Iterate over the processed abstracts and remove duplicates
|
593 |
+
seen_contents = set() # Set to track already seen contents
|
594 |
+
evidence_counter = 1
|
595 |
+
|
596 |
+
# Display the results of the processed abstracts with numbered expanders
|
597 |
+
st.markdown("### **Scientific Evidence**")
|
598 |
+
|
599 |
+
# Add a legend for the colors
|
600 |
+
legend_html = """
|
601 |
+
<div style="display: flex; flex-direction: column; align-items: flex-start;">
|
602 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
603 |
+
<div style="width: 20px; height: 20px; background-color: lightgreen; margin-right: 10px; border-radius: 5px;"></div>
|
604 |
+
<div>Positive Evidence</div>
|
605 |
+
</div>
|
606 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
607 |
+
<div style="width: 20px; height: 20px; background-color: red; margin-right: 10px; border-radius: 5px;"></div>
|
608 |
+
<div>Negative Evidence</div>
|
609 |
+
</div>
|
610 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
611 |
+
<div style="width: 20px; height: 20px; background-color: yellow; margin-right: 10px; border-radius: 5px;"></div>
|
612 |
+
<div>Dubious Evidence</div>
|
613 |
+
</div>
|
614 |
+
</div>
|
615 |
+
"""
|
616 |
+
col1, col2 = st.columns([0.8, 0.2])
|
617 |
+
|
618 |
+
with col1:
|
619 |
+
if processed_abstracts:
|
620 |
+
tabs = st.tabs([f"Scientific Evidence {i}" for i in range(1, len(processed_abstracts) + 1)])
|
621 |
+
for tab, (name, content) in zip(tabs, processed_abstracts.items()):
|
622 |
+
if content not in seen_contents: # Check for duplicates
|
623 |
+
seen_contents.add(content)
|
624 |
+
with tab:
|
625 |
+
# Switch colors if the label is "False"
|
626 |
+
if predicted_label.lower() == "false":
|
627 |
+
content = content.replace("background-color: lightgreen", "background-color: tempcolor")
|
628 |
+
content = content.replace("background-color: red", "background-color: lightgreen")
|
629 |
+
content = content.replace("background-color: tempcolor", "background-color: red")
|
630 |
+
|
631 |
+
# Use `st.write` to display HTML directly
|
632 |
+
st.write(content, unsafe_allow_html=True)
|
633 |
+
else:
|
634 |
+
st.markdown("No relevant Scientific Evidence found")
|
635 |
+
|
636 |
+
with col2:
|
637 |
+
st.caption("Legend")
|
638 |
+
st.markdown(legend_html, unsafe_allow_html=True)
|
639 |
+
|
640 |
+
|
641 |
+
#### Web page check PAGE ####
|
642 |
+
elif page == "Page check":
|
643 |
+
st.subheader("Page check")
|
644 |
+
st.caption("✨ Enter a URL to fact-check the health-related claims on the page and hit the button to see the results! 🔍")
|
645 |
+
|
646 |
+
url = st.text_input("URL to fact-check:")
|
647 |
+
|
648 |
+
if st.button("✨ Fact Check") and url:
|
649 |
+
st.session_state.true_count = 0
|
650 |
+
st.session_state.false_count = 0
|
651 |
+
st.session_state.nei_count = 0
|
652 |
+
|
653 |
+
with st.spinner('🌐🔍 Extracting claims...'):
|
654 |
+
article_data = get_article_data(url)
|
655 |
+
|
656 |
+
try:
|
657 |
+
# Retrieve the claims from the article data
|
658 |
+
prompt_template = claim_detection_template(article_data)
|
659 |
+
|
660 |
+
# Call the API
|
661 |
+
completion = client.chat.completions.create(
|
662 |
+
model="meta/llama-3.1-405b-instruct",
|
663 |
+
messages=[{"role": "user", "content": prompt_template}],
|
664 |
+
temperature=0.1,
|
665 |
+
top_p=0.7,
|
666 |
+
max_tokens=1024,
|
667 |
+
stream=True
|
668 |
+
)
|
669 |
+
|
670 |
+
# Collect the response
|
671 |
+
answer = ""
|
672 |
+
for chunk in completion:
|
673 |
+
if chunk.choices[0].delta.content:
|
674 |
+
answer += chunk.choices[0].delta.content
|
675 |
+
|
676 |
+
# Debug: Controlla la risposta
|
677 |
+
print(f"{answer}")
|
678 |
+
|
679 |
+
except Exception as e:
|
680 |
+
print(f"Error {e}")
|
681 |
+
|
682 |
+
claims_dict = extract_and_split_claims(answer)
|
683 |
+
|
684 |
+
# Display the extracted claims
|
685 |
+
st.markdown("### **Claims Extracted**")
|
686 |
+
st.caption("🔍 Here are the health-related claims extracted from the page:")
|
687 |
+
cols = st.columns(3)
|
688 |
+
for i, (claim_key, claim_text) in enumerate(claims_dict.items(), 1):
|
689 |
+
col = cols[(i - 1) % 3]
|
690 |
+
with col.expander(f"Claim {i} 📝", expanded=True):
|
691 |
+
st.write(claim_text)
|
692 |
+
|
693 |
+
# Display the results for the extracted claims
|
694 |
+
st.markdown("### **Results**")
|
695 |
+
st.caption("🔍 Here are the results for the extracted claims:")
|
696 |
+
for claim_key, claim_text in claims_dict.items():
|
697 |
+
st.session_state.claim = claim_text
|
698 |
+
if st.session_state.claim:
|
699 |
+
top_abstracts = retrieve_top_abstracts(st.session_state.claim, model, index, pmids, data, top_k=5)
|
700 |
+
st.session_state.top_abstracts = top_abstracts # Salva i risultati
|
701 |
+
|
702 |
+
with st.expander(f"✔️ **Results for {claim_key}**", expanded=True):
|
703 |
+
for i, (abstract, pmid, distance) in enumerate(st.session_state.top_abstracts, 1):
|
704 |
+
pubmed_url = f"https://pubmed.ncbi.nlm.nih.gov/{pmid}/"
|
705 |
+
globals()[f"abstract_{i}"] = abstract
|
706 |
+
globals()[f"reference_{i}"] = pubmed_url
|
707 |
+
globals()[f"distance_{i}"] = distance
|
708 |
+
|
709 |
+
with st.spinner('🔍 We are checking...'):
|
710 |
+
try:
|
711 |
+
# Retrieve the question from the DataFrame
|
712 |
+
query = st.session_state.claim
|
713 |
+
|
714 |
+
# Generate the reasoning template
|
715 |
+
prompt_template = llm_reasoning_template(query)
|
716 |
+
|
717 |
+
# Add the abstracts to the prompt
|
718 |
+
for i in range(1, len(st.session_state.top_abstracts)):
|
719 |
+
prompt_template += f"{globals()[f'abstract_{i}']} ; "
|
720 |
+
prompt_template += f"{globals()[f'abstract_{i+1}']} [/INST]"
|
721 |
+
|
722 |
+
# Call the API
|
723 |
+
completion = client.chat.completions.create(
|
724 |
+
model="meta/llama-3.1-405b-instruct",
|
725 |
+
messages=[{"role": "user", "content": prompt_template}],
|
726 |
+
temperature=0.1,
|
727 |
+
top_p=0.7,
|
728 |
+
max_tokens=1024,
|
729 |
+
stream=True
|
730 |
+
)
|
731 |
+
|
732 |
+
# Collect the response
|
733 |
+
answer = ""
|
734 |
+
for chunk in completion:
|
735 |
+
if chunk.choices[0].delta.content:
|
736 |
+
answer += chunk.choices[0].delta.content
|
737 |
+
|
738 |
+
# Debug: Check the answer
|
739 |
+
if debug:
|
740 |
+
print(f"{answer}")
|
741 |
+
|
742 |
+
except Exception as e:
|
743 |
+
st.write(f"Error processing index: {e}")
|
744 |
+
|
745 |
+
with st.spinner('🤔💬 Justifying the check...'):
|
746 |
+
# Perform parsing and separate variables
|
747 |
+
zeroshot_classifier = pipeline(
|
748 |
+
"zero-shot-classification", model="MoritzLaurer/deberta-v3-large-zeroshot-v1.1-all-33"
|
749 |
+
)
|
750 |
+
first_label, justification, supporting, refusing, notes = parse_response(answer)
|
751 |
+
|
752 |
+
with st.spinner('🕵️♂️📜 We are finding evidence...'):
|
753 |
+
# Generate the justification for the claim
|
754 |
+
result = generate_justification(st.session_state.claim, justification)
|
755 |
+
predicted_label, score_label = extract_label_and_score(result)
|
756 |
+
|
757 |
+
# Update the counts based on the predicted label
|
758 |
+
if predicted_label == "True":
|
759 |
+
color = f"rgba(0, 204, 0, {score_label})" # Green
|
760 |
+
st.session_state.true_count += 1
|
761 |
+
elif predicted_label == "False":
|
762 |
+
color = f"rgba(204, 0, 0, {score_label})" # Red
|
763 |
+
st.session_state.false_count += 1
|
764 |
+
elif predicted_label == "NEI":
|
765 |
+
color = f"rgba(255, 255, 0, {score_label})" # Yellow
|
766 |
+
st.session_state.nei_count += 1
|
767 |
+
else:
|
768 |
+
color = "black" # Default color
|
769 |
+
|
770 |
+
confidence = f"{score_label * 100:.2f}%"
|
771 |
+
st.caption(f"📝 The Claim: {st.session_state.claim}")
|
772 |
+
st.markdown(
|
773 |
+
f"**Prediction of claim:** Most likely <span style='color: {color}; font-weight: bold;'>{predicted_label}</span> with a confidence of <span style='color: {color}; font-weight: bold;'>{confidence}</span>",
|
774 |
+
unsafe_allow_html=True
|
775 |
+
)
|
776 |
+
|
777 |
+
st.markdown("### **Justification**")
|
778 |
+
st.markdown(f'<p> {justification}</p>', unsafe_allow_html=True)
|
779 |
+
|
780 |
+
abstracts = {}
|
781 |
+
for i in range(1, len(st.session_state.top_abstracts) + 1):
|
782 |
+
abstracts[f"abstract_{i}"] = globals()[f"abstract_{i}"]
|
783 |
+
|
784 |
+
pattern = r'"\s*(.*?)\s*"\s*\(abstract_(\d+)\)'
|
785 |
+
|
786 |
+
supporting_texts = []
|
787 |
+
for item in supporting:
|
788 |
+
try:
|
789 |
+
supporting_texts.append(item["text"])
|
790 |
+
except (TypeError, KeyError):
|
791 |
+
continue
|
792 |
+
supporting = clean_phrases(supporting_texts, pattern)
|
793 |
+
|
794 |
+
refusing_text = []
|
795 |
+
for item in refusing:
|
796 |
+
try:
|
797 |
+
refusing_text.append(item["text"])
|
798 |
+
except (TypeError, KeyError):
|
799 |
+
continue
|
800 |
+
refusing = clean_phrases(refusing_text, pattern)
|
801 |
+
|
802 |
+
if debug:
|
803 |
+
print(supporting)
|
804 |
+
print(refusing)
|
805 |
+
|
806 |
+
processed_abstracts = {}
|
807 |
+
for abstract_name, abstract_text in abstracts.items():
|
808 |
+
# Highlight supporting phrases in green
|
809 |
+
supporting_matches = [phrase for phrase in supporting if phrase["abstract"] == abstract_name]
|
810 |
+
abstract_text = highlight_phrases(abstract_text, supporting_matches, "lightgreen", predicted_label)
|
811 |
+
|
812 |
+
# Highlight refusing phrases in red
|
813 |
+
refusing_matches = [phrase for phrase in refusing if phrase["abstract"] == abstract_name]
|
814 |
+
abstract_text = highlight_phrases(abstract_text, refusing_matches, "red", predicted_label)
|
815 |
+
|
816 |
+
# Add only if supporting matches are found
|
817 |
+
if supporting_matches:
|
818 |
+
# Add the reference if a corresponding variable exists
|
819 |
+
reference_variable = f"reference_{abstract_name.split('_')[1]}"
|
820 |
+
if reference_variable in globals():
|
821 |
+
reference_value = globals()[reference_variable]
|
822 |
+
abstract_text += f"<br><br><strong>🔗 Reference:</strong> {reference_value}"
|
823 |
+
|
824 |
+
# Add the processed abstract
|
825 |
+
processed_abstracts[abstract_name] = abstract_text
|
826 |
+
|
827 |
+
# Iterate over the processed abstracts and remove duplicates
|
828 |
+
seen_contents = set() # Set to track already seen contents
|
829 |
+
evidence_counter = 1
|
830 |
+
|
831 |
+
# Display the results of the processed abstracts with numbered expanders
|
832 |
+
st.markdown("### **Scientific Evidence**")
|
833 |
+
|
834 |
+
# Add a legend for the colors
|
835 |
+
legend_html = """
|
836 |
+
<div style="display: flex; flex-direction: column; align-items: flex-start;">
|
837 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
838 |
+
<div style="width: 20px; height: 20px; background-color: lightgreen; margin-right: 10px; border-radius: 5px;"></div>
|
839 |
+
<div>Positive Evidence</div>
|
840 |
+
</div>
|
841 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
842 |
+
<div style="width: 20px; height: 20px; background-color: red; margin-right: 10px; border-radius: 5px;"></div>
|
843 |
+
<div>Negative Evidence</div>
|
844 |
+
</div>
|
845 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
846 |
+
<div style="width: 20px; height: 20px; background-color: yellow; margin-right: 10px; border-radius: 5px;"></div>
|
847 |
+
<div>Dubious Evidence</div>
|
848 |
+
</div>
|
849 |
+
</div>
|
850 |
+
"""
|
851 |
+
col1, col2 = st.columns([0.8, 0.2])
|
852 |
+
|
853 |
+
with col1:
|
854 |
+
if processed_abstracts:
|
855 |
+
tabs = st.tabs([f"Scientific Evidence {i}" for i in range(1, len(processed_abstracts) + 1)])
|
856 |
+
for tab, (name, content) in zip(tabs, processed_abstracts.items()):
|
857 |
+
if content not in seen_contents: # Check for duplicates
|
858 |
+
seen_contents.add(content)
|
859 |
+
with tab:
|
860 |
+
# Switch colors if the label is "False"
|
861 |
+
if predicted_label.lower() == "false":
|
862 |
+
content = content.replace("background-color: lightgreen", "background-color: tempcolor")
|
863 |
+
content = content.replace("background-color: red", "background-color: lightgreen")
|
864 |
+
content = content.replace("background-color: tempcolor", "background-color: red")
|
865 |
+
|
866 |
+
# Use `st.write` to display HTML directly
|
867 |
+
st.write(content, unsafe_allow_html=True)
|
868 |
+
else:
|
869 |
+
st.markdown("No relevant Scientific Evidence found")
|
870 |
+
|
871 |
+
with col2:
|
872 |
+
st.caption("Legend")
|
873 |
+
st.markdown(legend_html, unsafe_allow_html=True)
|
874 |
+
|
875 |
+
st.markdown("### **Page Summary**")
|
876 |
+
st.caption("📊 Here is a summary of the results for the extracted claims:")
|
877 |
+
|
878 |
+
# Labels and Colors
|
879 |
+
labels = ['True', 'False', 'NEI']
|
880 |
+
colors = ['green', 'red', 'yellow']
|
881 |
+
|
882 |
+
# Sizes of the pie chart
|
883 |
+
sizes = [
|
884 |
+
st.session_state.true_count,
|
885 |
+
st.session_state.false_count,
|
886 |
+
st.session_state.nei_count
|
887 |
+
]
|
888 |
+
|
889 |
+
# Configure the Pie Chart Options
|
890 |
+
options = {
|
891 |
+
"tooltip": {"trigger": "item"},
|
892 |
+
"legend": {"top": "5%", "left": "center"},
|
893 |
+
"series": [
|
894 |
+
{
|
895 |
+
"name": "Document Status",
|
896 |
+
"type": "pie",
|
897 |
+
"radius": ["40%", "70%"],
|
898 |
+
"avoidLabelOverlap": False,
|
899 |
+
"itemStyle": {
|
900 |
+
"borderRadius": 10,
|
901 |
+
"borderColor": "#fff",
|
902 |
+
"borderWidth": 2,
|
903 |
+
},
|
904 |
+
"label": {"show": True, "position": "center"},
|
905 |
+
"emphasis": {
|
906 |
+
"label": {"show": True, "fontSize": "20", "fontWeight": "bold"}
|
907 |
+
},
|
908 |
+
"labelLine": {"show": False},
|
909 |
+
"data": [
|
910 |
+
{"value": sizes[0], "name": labels[0], "itemStyle": {"color": colors[0]}},
|
911 |
+
{"value": sizes[1], "name": labels[1], "itemStyle": {"color": colors[1]}},
|
912 |
+
{"value": sizes[2], "name": labels[2], "itemStyle": {"color": colors[2]}},
|
913 |
+
],
|
914 |
+
}
|
915 |
+
],
|
916 |
+
}
|
917 |
+
|
918 |
+
# Display the Pie Chart
|
919 |
+
st1, st2 = st.columns([0.6, 0.4])
|
920 |
+
|
921 |
+
with st1:
|
922 |
+
st.markdown("#### The page is :")
|
923 |
+
true_count = st.session_state.true_count
|
924 |
+
false_count = st.session_state.false_count
|
925 |
+
nei_count = st.session_state.nei_count
|
926 |
+
|
927 |
+
if true_count > 0 and false_count == 0:
|
928 |
+
reliability = '<span style="color: darkgreen; font-weight: bold;">Highly Reliable</span>'
|
929 |
+
elif true_count > false_count:
|
930 |
+
reliability = '<span style="color: lightgreen; font-weight: bold;">Fairly Reliable</span>'
|
931 |
+
elif true_count == 0:
|
932 |
+
reliability = '<span style="color: darkred; font-weight: bold;">Strongly Considered Unreliable</span>'
|
933 |
+
elif false_count > true_count:
|
934 |
+
reliability = '<span style="color: lightcoral; font-weight: bold;">Unlikely to be Reliable</span>'
|
935 |
+
elif (true_count == false_count) or (nei_count > true_count and nei_count > false_count and true_count != 0 and false_count != 0):
|
936 |
+
reliability = '<span style="color: yellow; font-weight: bold;">NEI</span>'
|
937 |
+
else:
|
938 |
+
reliability = '<span style="color: black; font-weight: bold;">Completely Reliable</span>'
|
939 |
+
|
940 |
+
st.markdown(f"The page is considered {reliability} because it contains {true_count} true claims, {false_count} false claims, and {nei_count} claims with not enough information.", unsafe_allow_html=True)
|
941 |
+
|
942 |
+
with st.popover("ℹ️ Understanding the Truthfulness Ratings"):
|
943 |
+
st.markdown("""
|
944 |
+
The reliability of the page is determined based on the number of true and false claims extracted from the page.
|
945 |
+
- If the page contains only true claims, it is considered **Highly Reliable**.
|
946 |
+
- If the page has more true claims than false claims, it is considered **Fairly Reliable**.
|
947 |
+
-If the page has more false claims than true claims, it is considered **Unlikely to be Reliable**.
|
948 |
+
- If the page contains only false claims, it is considered **Strongly Considered Unreliable**.
|
949 |
+
- If the page has an equal number of true and false claims, it is considered **NEI**.
|
950 |
+
""")
|
951 |
+
|
952 |
+
with st2:
|
953 |
+
st_echarts(
|
954 |
+
options=options, height="500px",
|
955 |
+
)
|
956 |
+
|
957 |
+
|
958 |
+
#### Video check PAGE ####
|
959 |
+
elif page == "Video check":
|
960 |
+
st.subheader("Video claim check")
|
961 |
+
st.caption("✨ Upload a video to fact-check and hit the button to see the results! 🔍")
|
962 |
+
|
963 |
+
video = st.file_uploader("Choose a video...", type=["mp4"])
|
964 |
+
video_box, text_box = st.columns([0.6, 0.4])
|
965 |
+
if video is not None:
|
966 |
+
with video_box:
|
967 |
+
with st.expander("▶️ See uploaded video", expanded=False):
|
968 |
+
st.video(video)
|
969 |
+
|
970 |
+
if st.button("✨ Fact Check") and video is not None:
|
971 |
+
with st.spinner('🎥🔄 Processing video...'):
|
972 |
+
# Save the video to a temporary file
|
973 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as temp_video:
|
974 |
+
temp_video.write(video.read())
|
975 |
+
temp_video_path = temp_video.name
|
976 |
+
|
977 |
+
# Extract the audio from the video
|
978 |
+
temp_audio_path = tempfile.NamedTemporaryFile(delete=False, suffix=".wav").name
|
979 |
+
ffmpeg.input(temp_video_path).output(temp_audio_path, acodec="pcm_s16le", ar=16000, ac=1).run(overwrite_output=True)
|
980 |
+
|
981 |
+
# Transcribe the audio
|
982 |
+
model1 = whisper.load_model("small")
|
983 |
+
result = model1.transcribe(temp_audio_path)
|
984 |
+
|
985 |
+
# Extract the final text
|
986 |
+
transcribed_text = result["text"]
|
987 |
+
with text_box:
|
988 |
+
with st.expander("📝 Transcribed Text", expanded=False):
|
989 |
+
st.caption("🔍 Here is the transcribed text from the uploaded video:")
|
990 |
+
container = st.container(height=322)
|
991 |
+
container.write(transcribed_text)
|
992 |
+
|
993 |
+
st.session_state.true_count = 0
|
994 |
+
st.session_state.false_count = 0
|
995 |
+
st.session_state.nei_count = 0
|
996 |
+
|
997 |
+
with st.spinner('🌐🔍 Extracting claims from video...'):
|
998 |
+
try:
|
999 |
+
# Retrieve the claims from the video
|
1000 |
+
prompt_template = claim_detection_template(transcribed_text)
|
1001 |
+
|
1002 |
+
# Call the API
|
1003 |
+
completion = client.chat.completions.create(
|
1004 |
+
model="meta/llama-3.1-405b-instruct",
|
1005 |
+
messages=[{"role": "user", "content": prompt_template}],
|
1006 |
+
temperature=0.1,
|
1007 |
+
top_p=0.7,
|
1008 |
+
max_tokens=1024,
|
1009 |
+
stream=True
|
1010 |
+
)
|
1011 |
+
|
1012 |
+
# Collect the response
|
1013 |
+
answer = ""
|
1014 |
+
for chunk in completion:
|
1015 |
+
if chunk.choices[0].delta.content:
|
1016 |
+
answer += chunk.choices[0].delta.content
|
1017 |
+
|
1018 |
+
# Debug: Check the answer
|
1019 |
+
if debug:
|
1020 |
+
print(f"{answer}")
|
1021 |
+
|
1022 |
+
except Exception as e:
|
1023 |
+
print(f"Error {e}")
|
1024 |
+
|
1025 |
+
claims_dict = extract_and_split_claims(answer)
|
1026 |
+
|
1027 |
+
# Display the extracted claims
|
1028 |
+
st.markdown("### **Claims Extracted**")
|
1029 |
+
st.caption("🔍 Here are the health-related claims extracted from the video:")
|
1030 |
+
cols = st.columns(3)
|
1031 |
+
for i, (claim_key, claim_text) in enumerate(claims_dict.items(), 1):
|
1032 |
+
col = cols[(i - 1) % 3]
|
1033 |
+
with col.expander(f"Claim {i} 📝", expanded=True):
|
1034 |
+
st.write(claim_text)
|
1035 |
+
|
1036 |
+
# Display the results for the extracted claims
|
1037 |
+
st.markdown("### **Results**")
|
1038 |
+
st.caption("🔍 Here are the results for the extracted claims:")
|
1039 |
+
for claim_key, claim_text in claims_dict.items():
|
1040 |
+
st.session_state.claim = claim_text
|
1041 |
+
if st.session_state.claim:
|
1042 |
+
top_abstracts = retrieve_top_abstracts(st.session_state.claim, model, index, pmids, data, top_k=5)
|
1043 |
+
st.session_state.top_abstracts = top_abstracts # Salva i risultati
|
1044 |
+
|
1045 |
+
with st.expander(f"✔️ **Results for {claim_key}**", expanded=True):
|
1046 |
+
for i, (abstract, pmid, distance) in enumerate(st.session_state.top_abstracts, 1):
|
1047 |
+
pubmed_url = f"https://pubmed.ncbi.nlm.nih.gov/{pmid}/"
|
1048 |
+
globals()[f"abstract_{i}"] = abstract
|
1049 |
+
globals()[f"reference_{i}"] = pubmed_url
|
1050 |
+
globals()[f"distance_{i}"] = distance
|
1051 |
+
|
1052 |
+
with st.spinner('🔍 We are checking...'):
|
1053 |
+
try:
|
1054 |
+
# Retrieve the question from the DataFrame
|
1055 |
+
query = st.session_state.claim
|
1056 |
+
|
1057 |
+
# Generate the reasoning template
|
1058 |
+
prompt_template = llm_reasoning_template(query)
|
1059 |
+
|
1060 |
+
# Add the abstracts to the prompt
|
1061 |
+
for i in range(1, len(st.session_state.top_abstracts)):
|
1062 |
+
prompt_template += f"{globals()[f'abstract_{i}']} ; "
|
1063 |
+
prompt_template += f"{globals()[f'abstract_{i+1}']} [/INST]"
|
1064 |
+
|
1065 |
+
# Call the API
|
1066 |
+
completion = client.chat.completions.create(
|
1067 |
+
model="meta/llama-3.1-405b-instruct",
|
1068 |
+
messages=[{"role": "user", "content": prompt_template}],
|
1069 |
+
temperature=0.1,
|
1070 |
+
top_p=0.7,
|
1071 |
+
max_tokens=1024,
|
1072 |
+
stream=True
|
1073 |
+
)
|
1074 |
+
|
1075 |
+
# Collect the response
|
1076 |
+
answer = ""
|
1077 |
+
for chunk in completion:
|
1078 |
+
if chunk.choices[0].delta.content:
|
1079 |
+
answer += chunk.choices[0].delta.content
|
1080 |
+
|
1081 |
+
# Debug: Check the answer
|
1082 |
+
if debug:
|
1083 |
+
print(f"{answer}")
|
1084 |
+
|
1085 |
+
except Exception as e:
|
1086 |
+
st.write(f"Error processing index: {e}")
|
1087 |
+
|
1088 |
+
with st.spinner('🤔💬 Justifying the check...'):
|
1089 |
+
# Perform parsing and separate variables
|
1090 |
+
zeroshot_classifier = pipeline(
|
1091 |
+
"zero-shot-classification", model="MoritzLaurer/deberta-v3-large-zeroshot-v1.1-all-33"
|
1092 |
+
)
|
1093 |
+
first_label, justification, supporting, refusing, notes = parse_response(answer)
|
1094 |
+
|
1095 |
+
with st.spinner('🕵️♂️📜 We are finding evidence...'):
|
1096 |
+
# Generate the justification for the claim
|
1097 |
+
result = generate_justification(st.session_state.claim, justification)
|
1098 |
+
predicted_label, score_label = extract_label_and_score(result)
|
1099 |
+
|
1100 |
+
# Update the counts based on the predicted label
|
1101 |
+
if predicted_label == "True":
|
1102 |
+
color = f"rgba(0, 204, 0, {score_label})" # Green
|
1103 |
+
st.session_state.true_count += 1
|
1104 |
+
elif predicted_label == "False":
|
1105 |
+
color = f"rgba(204, 0, 0, {score_label})" # Red
|
1106 |
+
st.session_state.false_count += 1
|
1107 |
+
elif predicted_label == "NEI":
|
1108 |
+
color = f"rgba(255, 255, 0, {score_label})" # Yellow
|
1109 |
+
st.session_state.nei_count += 1
|
1110 |
+
else:
|
1111 |
+
color = "black" # Default color
|
1112 |
+
|
1113 |
+
confidence = f"{score_label * 100:.2f}%"
|
1114 |
+
st.caption(f"📝 The Claim: {st.session_state.claim}")
|
1115 |
+
st.markdown(
|
1116 |
+
f"**Prediction of claim:** Most likely <span style='color: {color}; font-weight: bold;'>{predicted_label}</span> with a confidence of <span style='color: {color}; font-weight: bold;'>{confidence}</span>",
|
1117 |
+
unsafe_allow_html=True
|
1118 |
+
)
|
1119 |
+
|
1120 |
+
st.markdown("### **Justification**")
|
1121 |
+
st.markdown(f'<p> {justification}</p>', unsafe_allow_html=True)
|
1122 |
+
|
1123 |
+
abstracts = {}
|
1124 |
+
for i in range(1, len(st.session_state.top_abstracts) + 1):
|
1125 |
+
abstracts[f"abstract_{i}"] = globals()[f"abstract_{i}"]
|
1126 |
+
|
1127 |
+
pattern = r'"\s*(.*?)\s*"\s*\(abstract_(\d+)\)'
|
1128 |
+
|
1129 |
+
supporting_texts = []
|
1130 |
+
for item in supporting:
|
1131 |
+
try:
|
1132 |
+
supporting_texts.append(item["text"])
|
1133 |
+
except (TypeError, KeyError):
|
1134 |
+
continue
|
1135 |
+
supporting = clean_phrases(supporting_texts, pattern)
|
1136 |
+
|
1137 |
+
refusing_text = []
|
1138 |
+
for item in refusing:
|
1139 |
+
try:
|
1140 |
+
refusing_text.append(item["text"])
|
1141 |
+
except (TypeError, KeyError):
|
1142 |
+
continue
|
1143 |
+
refusing = clean_phrases(refusing_text, pattern)
|
1144 |
+
|
1145 |
+
processed_abstracts = {}
|
1146 |
+
for abstract_name, abstract_text in abstracts.items():
|
1147 |
+
# Highlight supporting phrases in green
|
1148 |
+
supporting_matches = [phrase for phrase in supporting if phrase["abstract"] == abstract_name]
|
1149 |
+
abstract_text = highlight_phrases(abstract_text, supporting_matches, "lightgreen", predicted_label)
|
1150 |
+
|
1151 |
+
# Highlight refusing phrases in red
|
1152 |
+
refusing_matches = [phrase for phrase in refusing if phrase["abstract"] == abstract_name]
|
1153 |
+
abstract_text = highlight_phrases(abstract_text, refusing_matches, "red", predicted_label)
|
1154 |
+
|
1155 |
+
if supporting_matches:
|
1156 |
+
# Add the reference if a corresponding variable exists
|
1157 |
+
reference_variable = f"reference_{abstract_name.split('_')[1]}"
|
1158 |
+
if reference_variable in globals():
|
1159 |
+
reference_value = globals()[reference_variable]
|
1160 |
+
abstract_text += f"<br><br><strong>🔗 Reference:</strong> {reference_value}"
|
1161 |
+
|
1162 |
+
# Add the processed abstract
|
1163 |
+
processed_abstracts[abstract_name] = abstract_text
|
1164 |
+
|
1165 |
+
# Iterate over the processed abstracts and remove duplicates
|
1166 |
+
seen_contents = set() # Set to track already seen contents
|
1167 |
+
evidence_counter = 1
|
1168 |
+
|
1169 |
+
# Display the results of the processed abstracts with numbered expanders
|
1170 |
+
st.markdown("### **Scientific Evidence**")
|
1171 |
+
|
1172 |
+
# Add a legend for the colors
|
1173 |
+
legend_html = """
|
1174 |
+
<div style="display: flex; flex-direction: column; align-items: flex-start;">
|
1175 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
1176 |
+
<div style="width: 20px; height: 20px; background-color: lightgreen; margin-right: 10px; border-radius: 5px;"></div>
|
1177 |
+
<div>Positive Evidence</div>
|
1178 |
+
</div>
|
1179 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
1180 |
+
<div style="width: 20px; height: 20px; background-color: red; margin-right: 10px; border-radius: 5px;"></div>
|
1181 |
+
<div>Negative Evidence</div>
|
1182 |
+
</div>
|
1183 |
+
<div style="display: flex; align-items: center; margin-bottom: 5px;">
|
1184 |
+
<div style="width: 20px; height: 20px; background-color: yellow; margin-right: 10px; border-radius: 5px;"></div>
|
1185 |
+
<div>Dubious Evidence</div>
|
1186 |
+
</div>
|
1187 |
+
</div>
|
1188 |
+
"""
|
1189 |
+
col1, col2 = st.columns([0.8, 0.2])
|
1190 |
+
|
1191 |
+
with col1:
|
1192 |
+
if processed_abstracts:
|
1193 |
+
tabs = st.tabs([f"Scientific Evidence {i}" for i in range(1, len(processed_abstracts) + 1)])
|
1194 |
+
for tab, (name, content) in zip(tabs, processed_abstracts.items()):
|
1195 |
+
if content not in seen_contents: # Check for duplicates
|
1196 |
+
seen_contents.add(content)
|
1197 |
+
with tab:
|
1198 |
+
# Switch colors if the label is "False"
|
1199 |
+
if predicted_label.lower() == "false":
|
1200 |
+
content = content.replace("background-color: lightgreen", "background-color: tempcolor")
|
1201 |
+
content = content.replace("background-color: red", "background-color: lightgreen")
|
1202 |
+
content = content.replace("background-color: tempcolor", "background-color: red")
|
1203 |
+
|
1204 |
+
# Use `st.write` to display HTML directly
|
1205 |
+
st.write(content, unsafe_allow_html=True)
|
1206 |
+
else:
|
1207 |
+
st.markdown("No relevant Scientific Evidence found")
|
1208 |
+
|
1209 |
+
with col2:
|
1210 |
+
st.caption("Legend")
|
1211 |
+
st.markdown(legend_html, unsafe_allow_html=True)
|
1212 |
+
|
1213 |
+
st.markdown("### **Video Summary**")
|
1214 |
+
st.caption("📊 Here is a summary of the results for the extracted claims:")
|
1215 |
+
|
1216 |
+
# Labels and Colors
|
1217 |
+
labels = ['True', 'False', 'NEI']
|
1218 |
+
colors = ['green', 'red', 'yellow']
|
1219 |
+
|
1220 |
+
# Sizes of the pie chart
|
1221 |
+
sizes = [
|
1222 |
+
st.session_state.true_count,
|
1223 |
+
st.session_state.false_count,
|
1224 |
+
st.session_state.nei_count
|
1225 |
+
]
|
1226 |
+
|
1227 |
+
# Configure the Pie Chart Options
|
1228 |
+
options = {
|
1229 |
+
"tooltip": {"trigger": "item"},
|
1230 |
+
"legend": {"top": "5%", "left": "center"},
|
1231 |
+
"series": [
|
1232 |
+
{
|
1233 |
+
"name": "Document Status",
|
1234 |
+
"type": "pie",
|
1235 |
+
"radius": ["40%", "70%"],
|
1236 |
+
"avoidLabelOverlap": False,
|
1237 |
+
"itemStyle": {
|
1238 |
+
"borderRadius": 10,
|
1239 |
+
"borderColor": "#fff",
|
1240 |
+
"borderWidth": 2,
|
1241 |
+
},
|
1242 |
+
"label": {"show": True, "position": "center"},
|
1243 |
+
"emphasis": {
|
1244 |
+
"label": {"show": True, "fontSize": "20", "fontWeight": "bold"}
|
1245 |
+
},
|
1246 |
+
"labelLine": {"show": False},
|
1247 |
+
"data": [
|
1248 |
+
{"value": sizes[0], "name": labels[0], "itemStyle": {"color": colors[0]}},
|
1249 |
+
{"value": sizes[1], "name": labels[1], "itemStyle": {"color": colors[1]}},
|
1250 |
+
{"value": sizes[2], "name": labels[2], "itemStyle": {"color": colors[2]}},
|
1251 |
+
],
|
1252 |
+
}
|
1253 |
+
],
|
1254 |
+
}
|
1255 |
+
|
1256 |
+
# Display the Pie Chart
|
1257 |
+
st1, st2 = st.columns([0.6, 0.4])
|
1258 |
+
|
1259 |
+
with st1:
|
1260 |
+
st.markdown("#### The Video is :")
|
1261 |
+
true_count = st.session_state.true_count
|
1262 |
+
false_count = st.session_state.false_count
|
1263 |
+
nei_count = st.session_state.nei_count
|
1264 |
+
|
1265 |
+
if true_count > 0 and false_count == 0:
|
1266 |
+
reliability = '<span style="color: darkgreen; font-weight: bold;">Highly Reliable</span>'
|
1267 |
+
elif true_count > false_count:
|
1268 |
+
reliability = '<span style="color: lightgreen; font-weight: bold;">Fairly Reliable</span>'
|
1269 |
+
elif true_count == 0:
|
1270 |
+
reliability = '<span style="color: darkred; font-weight: bold;">Strongly Considered Unreliable</span>'
|
1271 |
+
elif false_count > true_count:
|
1272 |
+
reliability = '<span style="color: lightcoral; font-weight: bold;">Unlikely to be Reliable</span>'
|
1273 |
+
elif (true_count == false_count) or (nei_count > true_count and nei_count > false_count and true_count != 0 and false_count != 0):
|
1274 |
+
reliability = '<span style="color: yellow; font-weight: bold;">NEI</span>'
|
1275 |
+
else:
|
1276 |
+
reliability = '<span style="color: black; font-weight: bold;">Completely Reliable</span>'
|
1277 |
+
|
1278 |
+
st.markdown(f"The video is considered {reliability} because it contains {true_count} true claims, {false_count} false claims, and {nei_count} claims with not enough information.", unsafe_allow_html=True)
|
1279 |
+
|
1280 |
+
with st.popover("ℹ️ Understanding the Truthfulness Ratings"):
|
1281 |
+
st.markdown("""
|
1282 |
+
The reliability of the video is determined based on the number of true and false claims extracted from the video.
|
1283 |
+
- If the video contains only true claims, it is considered **Highly Reliable**.
|
1284 |
+
- If the video has more true claims than false claims, it is considered **Fairly Reliable**.
|
1285 |
+
- If the video has more false claims than true claims, it is considered **Unlikely to be Reliable**.
|
1286 |
+
- If the video contains only false claims, it is considered **Strongly Considered Unreliable**.
|
1287 |
+
- If the video has an equal number of true and false claims, it is considered **NEI**.
|
1288 |
+
""")
|
1289 |
+
|
1290 |
+
with st2:
|
1291 |
+
st_echarts(
|
1292 |
+
options=options, height="500px",
|
1293 |
+
)
|
1294 |
+
|
requirements.txt
ADDED
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
ago==0.0.95
|
2 |
+
aiohappyeyeballs==2.4.4
|
3 |
+
aiohttp==3.11.11
|
4 |
+
aiosignal==1.3.2
|
5 |
+
altair==5.5.0
|
6 |
+
annotated-types==0.7.0
|
7 |
+
anyio==4.8.0
|
8 |
+
async-timeout==5.0.1
|
9 |
+
attrs==24.3.0
|
10 |
+
Automat==24.8.1
|
11 |
+
beautifulsoup4==4.12.3
|
12 |
+
blinker==1.9.0
|
13 |
+
boto3==1.36.5
|
14 |
+
botocore==1.36.5
|
15 |
+
bs4==0.0.2
|
16 |
+
cachetools==5.5.0
|
17 |
+
certifi==2024.12.14
|
18 |
+
cffi==1.17.1
|
19 |
+
chardet==5.2.0
|
20 |
+
charset-normalizer==3.4.1
|
21 |
+
click==8.1.8
|
22 |
+
colorama==0.4.6
|
23 |
+
constantly==23.10.4
|
24 |
+
contourpy==1.3.0
|
25 |
+
cryptography==44.0.0
|
26 |
+
cssselect==1.2.0
|
27 |
+
cycler==0.12.1
|
28 |
+
decorator==5.1.1
|
29 |
+
defusedxml==0.7.1
|
30 |
+
distro==1.9.0
|
31 |
+
dotmap==1.3.30
|
32 |
+
elastic-transport==8.17.0
|
33 |
+
elasticsearch==8.17.1
|
34 |
+
exceptiongroup==1.2.2
|
35 |
+
faiss-cpu==1.7.4
|
36 |
+
faust-cchardet==2.1.19
|
37 |
+
feedparser==6.0.11
|
38 |
+
ffmpeg-python==0.2.0
|
39 |
+
filelock==3.16.1
|
40 |
+
fonttools==4.55.5
|
41 |
+
frozenlist==1.5.0
|
42 |
+
fsspec==2024.12.0
|
43 |
+
future==1.0.0
|
44 |
+
gitdb==4.0.12
|
45 |
+
GitPython==3.1.44
|
46 |
+
h11==0.14.0
|
47 |
+
hjson==3.1.0
|
48 |
+
htbuilder==0.9.0
|
49 |
+
httpcore==1.0.7
|
50 |
+
httpx==0.28.1
|
51 |
+
huggingface-hub==0.17.3
|
52 |
+
hurry.filesize==0.9
|
53 |
+
hyperlink==21.0.0
|
54 |
+
idna==3.10
|
55 |
+
imageio==2.37.0
|
56 |
+
imageio-ffmpeg==0.6.0
|
57 |
+
importlib-metadata==6.11.0
|
58 |
+
incremental==24.7.2
|
59 |
+
itemadapter==0.10.0
|
60 |
+
itemloaders==1.3.2
|
61 |
+
Jinja2==3.1.5
|
62 |
+
jiter==0.8.2
|
63 |
+
jmespath==1.0.1
|
64 |
+
joblib==1.4.2
|
65 |
+
jsonschema==4.23.0
|
66 |
+
jsonschema-specifications==2024.10.1
|
67 |
+
kiwisolver==1.4.7
|
68 |
+
langdetect==1.0.9
|
69 |
+
llvmlite==0.43.0
|
70 |
+
lxml==5.3.0
|
71 |
+
lxml_html_clean==0.4.1
|
72 |
+
markdown-it-py==3.0.0
|
73 |
+
MarkupSafe==3.0.2
|
74 |
+
matplotlib==3.9.4
|
75 |
+
mdurl==0.1.2
|
76 |
+
more-itertools==10.6.0
|
77 |
+
moviepy==2.1.2
|
78 |
+
mpmath==1.3.0
|
79 |
+
multidict==6.1.0
|
80 |
+
narwhals==1.22.0
|
81 |
+
networkx==3.2.1
|
82 |
+
news-please==1.6.13
|
83 |
+
newspaper4k==0.9.3.1
|
84 |
+
nltk==3.9.1
|
85 |
+
numba==0.60.0
|
86 |
+
numpy==1.26.4
|
87 |
+
openai==1.59.9
|
88 |
+
openai-whisper==20240930
|
89 |
+
packaging==23.2
|
90 |
+
pandas==2.0.3
|
91 |
+
parsel==1.10.0
|
92 |
+
Pillow==9.5.0
|
93 |
+
plac==1.4.3
|
94 |
+
prettytable==3.13.0
|
95 |
+
proglog==0.1.10
|
96 |
+
propcache==0.2.1
|
97 |
+
Protego==0.4.0
|
98 |
+
protobuf==4.25.5
|
99 |
+
psycopg2-binary==2.9.10
|
100 |
+
pyarrow==19.0.0
|
101 |
+
pyasn1==0.6.1
|
102 |
+
pyasn1_modules==0.4.1
|
103 |
+
pycparser==2.22
|
104 |
+
pydantic==2.10.5
|
105 |
+
pydantic_core==2.27.2
|
106 |
+
pydeck==0.9.1
|
107 |
+
PyDispatcher==2.0.7
|
108 |
+
pyecharts==2.0.8
|
109 |
+
Pygments==2.19.1
|
110 |
+
Pympler==1.1
|
111 |
+
PyMySQL==1.1.1
|
112 |
+
pyOpenSSL==25.0.0
|
113 |
+
pyparsing==3.2.1
|
114 |
+
python-dateutil==2.9.0.post0
|
115 |
+
python-dotenv==1.0.1
|
116 |
+
pytz==2024.2
|
117 |
+
pytz-deprecation-shim==0.1.0.post0
|
118 |
+
pywin32==308
|
119 |
+
PyYAML==6.0.2
|
120 |
+
queuelib==1.7.0
|
121 |
+
readability-lxml==0.8.1
|
122 |
+
redis==5.2.1
|
123 |
+
referencing==0.35.1
|
124 |
+
regex==2024.11.6
|
125 |
+
requests==2.32.3
|
126 |
+
requests-file==2.1.0
|
127 |
+
rich==13.9.4
|
128 |
+
rpds-py==0.22.3
|
129 |
+
s3transfer==0.11.2
|
130 |
+
safetensors==0.5.2
|
131 |
+
scikit-learn==1.6.1
|
132 |
+
scipy==1.13.1
|
133 |
+
Scrapy==2.12.0
|
134 |
+
sentence-transformers==2.2.2
|
135 |
+
sentencepiece==0.2.0
|
136 |
+
service-identity==24.2.0
|
137 |
+
sgmllib3k==1.0.0
|
138 |
+
simplejson==3.19.3
|
139 |
+
six==1.17.0
|
140 |
+
smmap==5.0.2
|
141 |
+
sniffio==1.3.1
|
142 |
+
soupsieve==2.6
|
143 |
+
st-annotated-text==4.0.1
|
144 |
+
streamlit==1.41.1
|
145 |
+
streamlit-echarts==0.4.0
|
146 |
+
streamlit-option-menu==0.4.0
|
147 |
+
streamlit-scrollable-textbox==0.0.3
|
148 |
+
sympy==1.13.1
|
149 |
+
tenacity==8.5.0
|
150 |
+
threadpoolctl==3.5.0
|
151 |
+
tiktoken==0.8.0
|
152 |
+
tldextract==5.1.3
|
153 |
+
tokenizers==0.14.1
|
154 |
+
toml==0.10.2
|
155 |
+
tomli==2.2.1
|
156 |
+
torch==2.5.1
|
157 |
+
torchvision==0.20.1
|
158 |
+
tornado==6.4.2
|
159 |
+
tqdm==4.67.1
|
160 |
+
transformers==4.34.0
|
161 |
+
Twisted==24.11.0
|
162 |
+
typing_extensions==4.12.2
|
163 |
+
tzdata==2024.2
|
164 |
+
tzlocal==4.3.1
|
165 |
+
urllib3==1.26.2
|
166 |
+
utils==1.0.2
|
167 |
+
validators==0.34.0
|
168 |
+
w3lib==2.2.1
|
169 |
+
warcio==1.7.5
|
170 |
+
watchdog==6.0.0
|
171 |
+
wcwidth==0.2.13
|
172 |
+
yarl==1.18.3
|
173 |
+
zipp==3.21.0
|
174 |
+
zope.interface==7.2
|