Spaces:
Build error
Build error
Upload folder using huggingface_hub
Browse files- .gitignore +166 -0
- README.md +85 -7
- __init__.py +0 -0
- api/__init__.py +0 -0
- api/routes.py +0 -0
- chat_app.py +426 -0
- chat_history.db +0 -0
- config.py +43 -0
- core/__init__.py +0 -0
- core/document_processor.py +0 -0
- core/download_dataset.py +440 -0
- core/pineconeqa.py +142 -0
- core/rag_engine.py +242 -0
- core/s3_utils.py +267 -0
- core/voice_processor.py +73 -0
- database/__init__.py +0 -0
- database/vector_store.py +0 -0
- docker/Dockerfile +32 -0
- docker/docker-compose.yaml +0 -0
- main.py +20 -0
- qa_app.py +147 -0
- rag_existing_index.ipynb +191 -0
- requirements.txt +12 -0
- sandbox_testing.ipynb +1073 -0
- test_download_data.sh +39 -0
- tests/__init__.py +0 -0
- tests/test_pinecone.py +152 -0
- tests/test_pinecone_embeddings.ipynb +386 -0
- tests/test_pinecone_rag.py +166 -0
- tests/test_rag_pdf.ipynb +204 -0
- todo.md +2 -0
- utils/__init__.py +0 -0
- utils/helpers.py +0 -0
- utils/models.py +94 -0
.gitignore
ADDED
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Byte-compiled / optimized / DLL files
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
|
6 |
+
# C extensions
|
7 |
+
*.so
|
8 |
+
|
9 |
+
# Distribution / packaging
|
10 |
+
.Python
|
11 |
+
build/
|
12 |
+
develop-eggs/
|
13 |
+
dist/
|
14 |
+
downloads/
|
15 |
+
eggs/
|
16 |
+
.eggs/
|
17 |
+
lib/
|
18 |
+
lib64/
|
19 |
+
parts/
|
20 |
+
sdist/
|
21 |
+
var/
|
22 |
+
wheels/
|
23 |
+
share/python-wheels/
|
24 |
+
*.egg-info/
|
25 |
+
.installed.cfg
|
26 |
+
*.egg
|
27 |
+
MANIFEST
|
28 |
+
|
29 |
+
# PyInstaller
|
30 |
+
# Usually these files are written by a python script from a template
|
31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
32 |
+
*.manifest
|
33 |
+
*.spec
|
34 |
+
|
35 |
+
# Installer logs
|
36 |
+
pip-log.txt
|
37 |
+
pip-delete-this-directory.txt
|
38 |
+
|
39 |
+
# Unit test / coverage reports
|
40 |
+
htmlcov/
|
41 |
+
.tox/
|
42 |
+
.nox/
|
43 |
+
.coverage
|
44 |
+
.coverage.*
|
45 |
+
.cache
|
46 |
+
nosetests.xml
|
47 |
+
coverage.xml
|
48 |
+
*.cover
|
49 |
+
*.py,cover
|
50 |
+
.hypothesis/
|
51 |
+
.pytest_cache/
|
52 |
+
cover/
|
53 |
+
|
54 |
+
# Translations
|
55 |
+
*.mo
|
56 |
+
*.pot
|
57 |
+
|
58 |
+
# Django stuff:
|
59 |
+
*.log
|
60 |
+
local_settings.py
|
61 |
+
db.sqlite3
|
62 |
+
db.sqlite3-journal
|
63 |
+
|
64 |
+
# Flask stuff:
|
65 |
+
instance/
|
66 |
+
.webassets-cache
|
67 |
+
|
68 |
+
# Scrapy stuff:
|
69 |
+
.scrapy
|
70 |
+
|
71 |
+
# Sphinx documentation
|
72 |
+
docs/_build/
|
73 |
+
|
74 |
+
# PyBuilder
|
75 |
+
.pybuilder/
|
76 |
+
target/
|
77 |
+
|
78 |
+
# Jupyter Notebook
|
79 |
+
.ipynb_checkpoints
|
80 |
+
|
81 |
+
# IPython
|
82 |
+
profile_default/
|
83 |
+
ipython_config.py
|
84 |
+
|
85 |
+
# pyenv
|
86 |
+
# For a library or package, you might want to ignore these files since the code is
|
87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
88 |
+
# .python-version
|
89 |
+
|
90 |
+
# pipenv
|
91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
94 |
+
# install all needed dependencies.
|
95 |
+
#Pipfile.lock
|
96 |
+
|
97 |
+
# poetry
|
98 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
100 |
+
# commonly ignored for libraries.
|
101 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
102 |
+
#poetry.lock
|
103 |
+
|
104 |
+
# pdm
|
105 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
106 |
+
#pdm.lock
|
107 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
108 |
+
# in version control.
|
109 |
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
110 |
+
.pdm.toml
|
111 |
+
.pdm-python
|
112 |
+
.pdm-build/
|
113 |
+
|
114 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
115 |
+
__pypackages__/
|
116 |
+
|
117 |
+
# Celery stuff
|
118 |
+
celerybeat-schedule
|
119 |
+
celerybeat.pid
|
120 |
+
|
121 |
+
# SageMath parsed files
|
122 |
+
*.sage.py
|
123 |
+
|
124 |
+
# Environments
|
125 |
+
.env
|
126 |
+
.venv
|
127 |
+
env/
|
128 |
+
venv/
|
129 |
+
ENV/
|
130 |
+
env.bak/
|
131 |
+
venv.bak/
|
132 |
+
|
133 |
+
# Spyder project settings
|
134 |
+
.spyderproject
|
135 |
+
.spyproject
|
136 |
+
|
137 |
+
# Rope project settings
|
138 |
+
.ropeproject
|
139 |
+
|
140 |
+
# mkdocs documentation
|
141 |
+
/site
|
142 |
+
|
143 |
+
# mypy
|
144 |
+
.mypy_cache/
|
145 |
+
.dmypy.json
|
146 |
+
dmypy.json
|
147 |
+
|
148 |
+
# Pyre type checker
|
149 |
+
.pyre/
|
150 |
+
|
151 |
+
# pytype static type analyzer
|
152 |
+
.pytype/
|
153 |
+
|
154 |
+
# Cython debug symbols
|
155 |
+
cython_debug/
|
156 |
+
|
157 |
+
# PyCharm
|
158 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
159 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
160 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
161 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
162 |
+
#.idea/
|
163 |
+
*.pem
|
164 |
+
*.m4a
|
165 |
+
downloaded_pdfs/
|
166 |
+
*env/
|
README.md
CHANGED
@@ -1,12 +1,90 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
|
4 |
-
colorFrom: indigo
|
5 |
-
colorTo: green
|
6 |
sdk: gradio
|
7 |
sdk_version: 5.6.0
|
8 |
-
app_file: app.py
|
9 |
-
pinned: false
|
10 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
+
title: sehatech-demo
|
3 |
+
app_file: chat_app.py
|
|
|
|
|
4 |
sdk: gradio
|
5 |
sdk_version: 5.6.0
|
|
|
|
|
6 |
---
|
7 |
+
# RAG Voice Boilerplate
|
8 |
+
|
9 |
+
A production-ready Python boilerplate for building RAG (Retrieval Augmented Generation) applications with voice processing capabilities.
|
10 |
+
|
11 |
+
## 🚀 Features
|
12 |
+
|
13 |
+
- 📚 RAG Engine Integration
|
14 |
+
- 🎤 Voice Processing Pipeline
|
15 |
+
- 🗄️ Vector Store Support
|
16 |
+
- 🐋 Docker Containerization
|
17 |
+
- 🧪 Testing Infrastructure
|
18 |
+
- 🔧 Modular Architecture
|
19 |
+
|
20 |
+
## 🏗️ Project Structure
|
21 |
+
```
|
22 |
+
├── app/
|
23 |
+
│ ├── __init__.py
|
24 |
+
│ ├── main.py
|
25 |
+
│ ├── config.py
|
26 |
+
│ ├── api/
|
27 |
+
│ │ ├── __init__.py
|
28 |
+
│ │ └── routes.py
|
29 |
+
│ ├── core/
|
30 |
+
│ │ ├── __init__.py
|
31 |
+
│ │ ├── rag_engine.py
|
32 |
+
│ │ ├── voice_processor.py
|
33 |
+
│ │ └── document_processor.py
|
34 |
+
│ ├── database/
|
35 |
+
│ │ ├── __init__.py
|
36 |
+
│ │ ├── vector_store.py
|
37 |
+
│ │ └── db.py
|
38 |
+
│ └── utils/
|
39 |
+
│ ├── __init__.py
|
40 |
+
│ └── helpers.py
|
41 |
+
├── tests/
|
42 |
+
│ └── __init__.py
|
43 |
+
├── docker/
|
44 |
+
│ ├── Dockerfile
|
45 |
+
│ └── docker-compose.yml
|
46 |
+
├── requirements.txt
|
47 |
+
└── README.md
|
48 |
+
```
|
49 |
+
|
50 |
+
## 🚦 Quick Start
|
51 |
+
|
52 |
+
|
53 |
+
#### Prerequisie
|
54 |
+
|
55 |
+
This project is only tested on python3.11
|
56 |
+
some points to consider:
|
57 |
+
langchain-pinecone works only between versions python3.8 and python3.13 exclusively
|
58 |
+
|
59 |
+
1. Clone the repository:
|
60 |
+
```bash
|
61 |
+
git clone https://github.com/yourusername/rag-voice-boilerplate.git
|
62 |
+
```
|
63 |
+
|
64 |
+
2. Install dependencies:
|
65 |
+
```bash
|
66 |
+
pip install -r requirements.txt
|
67 |
+
```
|
68 |
+
|
69 |
+
3. Run with Docker:
|
70 |
+
```bash
|
71 |
+
docker-compose up -d
|
72 |
+
```
|
73 |
+
|
74 |
+
## 📚 Documentation
|
75 |
+
|
76 |
+
### Core Components
|
77 |
+
|
78 |
+
- `rag_engine.py`: Handles retrieval augmented generation operations
|
79 |
+
- `voice_processor.py`: Processes audio input/output
|
80 |
+
- `document_processor.py`: Manages document parsing and preprocessing
|
81 |
+
- `vector_store.py`: Manages vector embeddings and similarity search
|
82 |
+
|
83 |
+
## 🤝 Contributing
|
84 |
+
|
85 |
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
86 |
+
|
87 |
+
## 📄 License
|
88 |
+
|
89 |
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
90 |
|
|
__init__.py
ADDED
File without changes
|
api/__init__.py
ADDED
File without changes
|
api/routes.py
ADDED
File without changes
|
chat_app.py
ADDED
@@ -0,0 +1,426 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from datetime import datetime
|
2 |
+
from core.pineconeqa import PineconeQA
|
3 |
+
import gradio as gr
|
4 |
+
from config import get_settings
|
5 |
+
from openai import OpenAI
|
6 |
+
from utils.models import DatabaseManager
|
7 |
+
import json
|
8 |
+
import hashlib
|
9 |
+
import tempfile
|
10 |
+
import os
|
11 |
+
|
12 |
+
class MedicalChatbot:
|
13 |
+
def __init__(self):
|
14 |
+
self.settings = get_settings()
|
15 |
+
self.qa_system = PineconeQA(
|
16 |
+
pinecone_api_key=self.settings.PINECONE_API_KEY,
|
17 |
+
openai_api_key=self.settings.OPENAI_API_KEY,
|
18 |
+
index_name=self.settings.INDEX_NAME
|
19 |
+
)
|
20 |
+
self.client = OpenAI(api_key=self.settings.OPENAI_API_KEY)
|
21 |
+
self.db = DatabaseManager()
|
22 |
+
self.current_doctor = None
|
23 |
+
self.current_session_id = None
|
24 |
+
|
25 |
+
def handle_session(self, doctor_name):
|
26 |
+
"""Create a new session if doctor name changes or no session exists"""
|
27 |
+
# Always create a new session
|
28 |
+
self.current_session_id = self.db.create_session(doctor_name)
|
29 |
+
self.current_doctor = doctor_name
|
30 |
+
return self.current_session_id
|
31 |
+
|
32 |
+
|
33 |
+
def get_user_identifier(self, request: gr.Request):
|
34 |
+
"""Create a unique user identifier from IP and user agent"""
|
35 |
+
if request is None:
|
36 |
+
return "anonymous"
|
37 |
+
|
38 |
+
identifier = f"{request.client.host}_{request.headers.get('User-Agent', 'unknown')}"
|
39 |
+
return hashlib.sha256(identifier.encode()).hexdigest()[:32]
|
40 |
+
|
41 |
+
def detect_message_type(self, message):
|
42 |
+
"""Use ChatGPT to detect if the message is a basic interaction or a knowledge query"""
|
43 |
+
try:
|
44 |
+
response = self.client.chat.completions.create(
|
45 |
+
model="gpt-4",
|
46 |
+
messages=[
|
47 |
+
{
|
48 |
+
"role": "system",
|
49 |
+
"content": """Analyze the following message and determine if it's:
|
50 |
+
1. A basic interaction like hello, thanks, how are you(greetings, thanks, farewell, etc.)
|
51 |
+
2. A question or request for information
|
52 |
+
return only 'basic' if the message is only for greeting, or return query
|
53 |
+
Respond with just the type: 'basic' or 'query'"""
|
54 |
+
},
|
55 |
+
{"role": "user", "content": message}
|
56 |
+
],
|
57 |
+
temperature=0.3,
|
58 |
+
max_tokens=10
|
59 |
+
)
|
60 |
+
return response.choices[0].message.content.strip().lower()
|
61 |
+
except Exception as e:
|
62 |
+
print(f'error encountered. returning query.\nError: {str(e)}')
|
63 |
+
return "query"
|
64 |
+
|
65 |
+
def get_chatgpt_response(self, message, history):
|
66 |
+
"""Get a response from ChatGPT"""
|
67 |
+
try:
|
68 |
+
chat_history = []
|
69 |
+
for human, assistant in history:
|
70 |
+
chat_history.extend([
|
71 |
+
{"role": "user", "content": human},
|
72 |
+
{"role": "assistant", "content": assistant}
|
73 |
+
])
|
74 |
+
|
75 |
+
messages = [
|
76 |
+
{
|
77 |
+
"role": "system",
|
78 |
+
"content": """ "You are an expert assistant for biomedical question-answering tasks. "
|
79 |
+
"You will be provided with context retrieved from medical literature."
|
80 |
+
"The medical literature is all from PubMed Open Access Articles. "
|
81 |
+
"Use this context to answer the question as accurately as possible. "
|
82 |
+
"The response might not be added precisely, so try to derive the answers from it as much as possible."
|
83 |
+
"If the context does not contain the required information, explain why. "
|
84 |
+
"Provide a concise and accurate answer """
|
85 |
+
}
|
86 |
+
] + chat_history + [
|
87 |
+
{"role": "user", "content": message}
|
88 |
+
]
|
89 |
+
|
90 |
+
response = self.client.chat.completions.create(
|
91 |
+
model="gpt-4",
|
92 |
+
messages=messages,
|
93 |
+
temperature=0.7,
|
94 |
+
max_tokens=500
|
95 |
+
)
|
96 |
+
return response.choices[0].message.content
|
97 |
+
except Exception as e:
|
98 |
+
return f"I apologize, but I encountered an error: {str(e)}"
|
99 |
+
|
100 |
+
def synthesize_answer(self, query, context_docs, history):
|
101 |
+
"""Synthesize an answer from multiple context documents using ChatGPT"""
|
102 |
+
try:
|
103 |
+
context = "\n\n".join([doc.page_content for doc in context_docs])
|
104 |
+
|
105 |
+
messages = [
|
106 |
+
{
|
107 |
+
"role": "system",
|
108 |
+
"content": """You are a medical expert assistant. Using the provided context,
|
109 |
+
synthesize a comprehensive, accurate answer. If the context doesn't contain
|
110 |
+
enough relevant information, say so and provide general medical knowledge.
|
111 |
+
Always maintain a professional yet accessible tone."""
|
112 |
+
},
|
113 |
+
{
|
114 |
+
"role": "user",
|
115 |
+
"content": f"""Context information:\n{context}\n\n
|
116 |
+
Based on this context and your medical knowledge, please answer the following question:\n{query}"""
|
117 |
+
}
|
118 |
+
]
|
119 |
+
|
120 |
+
response = self.client.chat.completions.create(
|
121 |
+
model="gpt-4",
|
122 |
+
messages=messages,
|
123 |
+
temperature=0.2,
|
124 |
+
max_tokens=1000
|
125 |
+
)
|
126 |
+
|
127 |
+
return response.choices[0].message.content
|
128 |
+
except Exception as e:
|
129 |
+
return f"I apologize, but I encountered an error synthesizing the answer: {str(e)}"
|
130 |
+
|
131 |
+
def format_sources_for_db(self, sources):
|
132 |
+
"""Format sources for database storage"""
|
133 |
+
if not sources:
|
134 |
+
return None
|
135 |
+
|
136 |
+
sources_data = []
|
137 |
+
for doc in sources:
|
138 |
+
sources_data.append({
|
139 |
+
'title': doc.metadata.get('title'),
|
140 |
+
'source': doc.metadata.get('source'),
|
141 |
+
'timestamp': datetime.utcnow().isoformat()
|
142 |
+
})
|
143 |
+
return json.dumps(sources_data)
|
144 |
+
|
145 |
+
def respond(self, message, history, doctor_name: str, request: gr.Request = None):
|
146 |
+
"""Main response function for the chatbot"""
|
147 |
+
try:
|
148 |
+
|
149 |
+
|
150 |
+
# Don't reuse sessions - ensure we're using the current session ID
|
151 |
+
if not hasattr(self, 'current_session_id') or not self.current_session_id:
|
152 |
+
self.current_session_id = self.db.create_session(doctor_name)
|
153 |
+
|
154 |
+
|
155 |
+
# Log user message
|
156 |
+
self.db.log_message(
|
157 |
+
session_id=self.current_session_id,
|
158 |
+
message=message,
|
159 |
+
is_user=True
|
160 |
+
)
|
161 |
+
|
162 |
+
# Rest of your existing respond method remains the same...
|
163 |
+
message_type = self.detect_message_type(message)
|
164 |
+
|
165 |
+
if message_type == "basic":
|
166 |
+
response = self.get_chatgpt_response(message, history)
|
167 |
+
self.db.log_message(
|
168 |
+
session_id=self.current_session_id,
|
169 |
+
message=response,
|
170 |
+
is_user=False
|
171 |
+
)
|
172 |
+
return response
|
173 |
+
|
174 |
+
retriever_response = self.qa_system.ask(message)
|
175 |
+
|
176 |
+
if "error" in retriever_response:
|
177 |
+
response = self.get_chatgpt_response(message, history)
|
178 |
+
self.db.log_message(
|
179 |
+
session_id=self.current_session_id,
|
180 |
+
message=response,
|
181 |
+
is_user=False
|
182 |
+
)
|
183 |
+
return response
|
184 |
+
|
185 |
+
if retriever_response.get("context") and len(retriever_response["context"]) > 0:
|
186 |
+
synthesized_answer = self.synthesize_answer(
|
187 |
+
message,
|
188 |
+
retriever_response["context"],
|
189 |
+
history
|
190 |
+
)
|
191 |
+
|
192 |
+
sources = self.format_sources(retriever_response["context"])
|
193 |
+
final_response = synthesized_answer + sources
|
194 |
+
|
195 |
+
self.db.log_message(
|
196 |
+
session_id=self.current_session_id,
|
197 |
+
message=final_response,
|
198 |
+
is_user=False,
|
199 |
+
sources=self.format_sources_for_db(retriever_response["context"])
|
200 |
+
)
|
201 |
+
return final_response
|
202 |
+
else:
|
203 |
+
response = self.get_chatgpt_response(message, history)
|
204 |
+
fallback_response = "I couldn't find specific information about this in my knowledge base, but here's what I can tell you:\n\n" + response
|
205 |
+
|
206 |
+
self.db.log_message(
|
207 |
+
session_id=self.current_session_id,
|
208 |
+
message=fallback_response,
|
209 |
+
is_user=False
|
210 |
+
)
|
211 |
+
return fallback_response
|
212 |
+
|
213 |
+
except Exception as e:
|
214 |
+
error_message = f"I apologize, but I encountered an error: {str(e)}"
|
215 |
+
if self.current_session_id:
|
216 |
+
self.db.log_message(
|
217 |
+
session_id=self.current_session_id,
|
218 |
+
message=error_message,
|
219 |
+
is_user=False
|
220 |
+
)
|
221 |
+
return error_message
|
222 |
+
|
223 |
+
def format_sources(self, sources):
|
224 |
+
"""Format sources into a readable string"""
|
225 |
+
if not sources:
|
226 |
+
return ""
|
227 |
+
|
228 |
+
formatted = "\n\n📚 Sources Used:\n"
|
229 |
+
seen_sources = set()
|
230 |
+
|
231 |
+
for doc in sources:
|
232 |
+
source_id = (doc.metadata.get('title', ''), doc.metadata.get('source', ''))
|
233 |
+
if source_id not in seen_sources:
|
234 |
+
seen_sources.add(source_id)
|
235 |
+
formatted += f"\n• {doc.metadata.get('title', 'Untitled')}\n"
|
236 |
+
if doc.metadata.get('source'):
|
237 |
+
formatted += f" Link: {doc.metadata['source']}\n"
|
238 |
+
|
239 |
+
return formatted
|
240 |
+
def transcribe_audio(self, audio_path):
|
241 |
+
"""Transcribe audio using OpenAI Whisper"""
|
242 |
+
try:
|
243 |
+
with open(audio_path, "rb") as audio_file:
|
244 |
+
transcript = self.client.audio.transcriptions.create(
|
245 |
+
model="whisper-1",
|
246 |
+
file=audio_file
|
247 |
+
)
|
248 |
+
return transcript.text
|
249 |
+
except Exception as e:
|
250 |
+
print(f"Error transcribing audio: {str(e)}")
|
251 |
+
return None
|
252 |
+
def process_audio_input(self, audio_path, history, doctor_name):
|
253 |
+
"""Process audio input and return both text and audio response"""
|
254 |
+
try:
|
255 |
+
# Transcribe the audio
|
256 |
+
transcription = self.transcribe_audio(audio_path)
|
257 |
+
if not transcription:
|
258 |
+
return "Sorry, I couldn't understand the audio.", None
|
259 |
+
|
260 |
+
# Get text response
|
261 |
+
text_response = self.respond(transcription, history, doctor_name)
|
262 |
+
|
263 |
+
# Convert response to speech
|
264 |
+
# audio_response = self.text_to_speech(text_response)
|
265 |
+
|
266 |
+
return text_response
|
267 |
+
except Exception as e:
|
268 |
+
return f"Error processing audio: {str(e)}"
|
269 |
+
def main():
|
270 |
+
med_chatbot = MedicalChatbot()
|
271 |
+
|
272 |
+
with gr.Blocks(theme=gr.themes.Soft()) as interface:
|
273 |
+
gr.Markdown("# Medical Knowledge Assistant")
|
274 |
+
gr.Markdown("Ask me anything about medical topics using text or voice.")
|
275 |
+
|
276 |
+
session_state = gr.State()
|
277 |
+
doctor_state = gr.State()
|
278 |
+
|
279 |
+
# Doctor Name Input
|
280 |
+
with gr.Row():
|
281 |
+
doctor_name = gr.Textbox(
|
282 |
+
label="Doctor Name",
|
283 |
+
placeholder="Enter your name",
|
284 |
+
show_label=True,
|
285 |
+
container=True,
|
286 |
+
scale=2,
|
287 |
+
interactive=True
|
288 |
+
)
|
289 |
+
|
290 |
+
# Main Chat Interface
|
291 |
+
with gr.Row():
|
292 |
+
with gr.Column(scale=4):
|
293 |
+
chatbot = gr.Chatbot(height=400)
|
294 |
+
|
295 |
+
# Text Input Area
|
296 |
+
with gr.Row():
|
297 |
+
text_input = gr.Textbox(
|
298 |
+
placeholder="Type your message here...",
|
299 |
+
scale=8
|
300 |
+
)
|
301 |
+
send_button = gr.Button("Send", scale=1)
|
302 |
+
|
303 |
+
# Audio Input Area
|
304 |
+
with gr.Row():
|
305 |
+
audio = gr.Audio(
|
306 |
+
sources=["microphone"],
|
307 |
+
type="filepath",
|
308 |
+
label="Voice Message",
|
309 |
+
interactive=True
|
310 |
+
)
|
311 |
+
|
312 |
+
# Audio Output Area
|
313 |
+
audio_output = gr.Audio(
|
314 |
+
label="AI Voice Response",
|
315 |
+
visible=True,
|
316 |
+
interactive=False
|
317 |
+
)
|
318 |
+
|
319 |
+
# Initialize session handler
|
320 |
+
def init_session(doctor, current_doctor):
|
321 |
+
if not doctor or doctor == current_doctor:
|
322 |
+
return None, current_doctor
|
323 |
+
return med_chatbot.db.create_session(doctor), doctor
|
324 |
+
|
325 |
+
# Text input handler
|
326 |
+
def on_text_submit(message, history, doctor, session_id, current_doctor):
|
327 |
+
if not session_id or doctor != current_doctor:
|
328 |
+
session_id, current_doctor = init_session(doctor, current_doctor)
|
329 |
+
|
330 |
+
med_chatbot.current_session_id = session_id
|
331 |
+
response = med_chatbot.respond(message, history, doctor)
|
332 |
+
history.append((message, response))
|
333 |
+
return "", history, None, session_id, current_doctor
|
334 |
+
|
335 |
+
# Audio input handler with numpy array
|
336 |
+
def on_audio_submit(audio_path, history, doctor, session_id, current_doctor):
|
337 |
+
try:
|
338 |
+
if audio_path is None:
|
339 |
+
return history, None, session_id, current_doctor
|
340 |
+
|
341 |
+
# Initialize session if needed
|
342 |
+
if not session_id or doctor != current_doctor:
|
343 |
+
session_id, current_doctor = init_session(doctor, current_doctor)
|
344 |
+
|
345 |
+
# Set current session
|
346 |
+
med_chatbot.current_session_id = session_id
|
347 |
+
|
348 |
+
# Transcribe the audio
|
349 |
+
transcription = med_chatbot.transcribe_audio(audio_path)
|
350 |
+
if not transcription:
|
351 |
+
return history, None, session_id, current_doctor
|
352 |
+
|
353 |
+
# Log the transcription as a user message in the database
|
354 |
+
med_chatbot.db.log_message(
|
355 |
+
session_id=session_id,
|
356 |
+
message=transcription,
|
357 |
+
is_user=True
|
358 |
+
)
|
359 |
+
|
360 |
+
# Append transcription to the chatbot history
|
361 |
+
history.append((f"🎤 {transcription}", None)) # User message, no AI response yet
|
362 |
+
|
363 |
+
# Process the transcription as a user query
|
364 |
+
ai_response = med_chatbot.respond(transcription, history, doctor)
|
365 |
+
|
366 |
+
# Append AI response to the chatbot history
|
367 |
+
history[-1] = (f"🎤 {transcription}", ai_response) # Update with AI response
|
368 |
+
|
369 |
+
# Log the AI response in the database
|
370 |
+
med_chatbot.db.log_message(
|
371 |
+
session_id=session_id,
|
372 |
+
message=ai_response,
|
373 |
+
is_user=False
|
374 |
+
)
|
375 |
+
|
376 |
+
return history, session_id, current_doctor
|
377 |
+
|
378 |
+
except Exception as e:
|
379 |
+
print(f"Error processing audio: {str(e)}")
|
380 |
+
return history, None, session_id, current_doctor
|
381 |
+
|
382 |
+
# Set up event handlers
|
383 |
+
doctor_name.submit(
|
384 |
+
fn=init_session,
|
385 |
+
inputs=[doctor_name, doctor_state],
|
386 |
+
outputs=[session_state, doctor_state]
|
387 |
+
)
|
388 |
+
|
389 |
+
send_button.click(
|
390 |
+
fn=on_text_submit,
|
391 |
+
inputs=[text_input, chatbot, doctor_name, session_state, doctor_state],
|
392 |
+
outputs=[text_input, chatbot, audio_output, session_state, doctor_state]
|
393 |
+
)
|
394 |
+
|
395 |
+
text_input.submit(
|
396 |
+
fn=on_text_submit,
|
397 |
+
inputs=[text_input, chatbot, doctor_name, session_state, doctor_state],
|
398 |
+
outputs=[text_input, chatbot, audio_output, session_state, doctor_state]
|
399 |
+
)
|
400 |
+
|
401 |
+
# Audio submission
|
402 |
+
audio.stop_recording(
|
403 |
+
fn=on_audio_submit,
|
404 |
+
inputs=[audio, chatbot, doctor_name, session_state, doctor_state],
|
405 |
+
outputs=[chatbot, session_state, doctor_state]
|
406 |
+
)
|
407 |
+
|
408 |
+
# Examples
|
409 |
+
gr.Examples(
|
410 |
+
examples=[
|
411 |
+
["Hello, how are you?", "Dr. Smith"],
|
412 |
+
["What are the common causes of iron deficiency anemia?", "Dr. Smith"],
|
413 |
+
["What are the latest treatments for type 2 diabetes?", "Dr. Smith"],
|
414 |
+
["Can you explain the relationship between diet and heart disease?", "Dr. Smith"]
|
415 |
+
],
|
416 |
+
inputs=[text_input, doctor_name]
|
417 |
+
)
|
418 |
+
|
419 |
+
interface.launch(
|
420 |
+
share=True,
|
421 |
+
server_name="0.0.0.0",
|
422 |
+
server_port=7860
|
423 |
+
)
|
424 |
+
|
425 |
+
if __name__ == "__main__":
|
426 |
+
main()
|
chat_history.db
ADDED
Binary file (20.5 kB). View file
|
|
config.py
ADDED
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from pydantic import validator
|
2 |
+
from pydantic_settings import BaseSettings
|
3 |
+
from functools import lru_cache
|
4 |
+
|
5 |
+
from dotenv import load_dotenv
|
6 |
+
|
7 |
+
|
8 |
+
class Settings(BaseSettings):
|
9 |
+
PROJECT_NAME: str = "SehaTech: Medical AI Assistant"
|
10 |
+
API_V1_STR: str = "/api/v1"
|
11 |
+
OPENAI_API_KEY: str
|
12 |
+
PINECONE_API_KEY: str
|
13 |
+
INDEX_NAME: str
|
14 |
+
CLOUD: str
|
15 |
+
REGION: str
|
16 |
+
PDF_DIRECTORY: str # Directory containing PDF files
|
17 |
+
CHUNK_SIZE: int
|
18 |
+
CHUNK_OVERLAP: int
|
19 |
+
DIMENSIONS: int
|
20 |
+
AWS_ACCESS_KEY: str
|
21 |
+
AWS_SECRET_KEY: str
|
22 |
+
AWS_REGION: str
|
23 |
+
AWS_BUCKET_NAME: str
|
24 |
+
PUBMED_BASE_URL: str
|
25 |
+
|
26 |
+
|
27 |
+
class Config:
|
28 |
+
env_file = ".env"
|
29 |
+
|
30 |
+
@validator("OPENAI_API_KEY")
|
31 |
+
def validate_openai_key(cls, v):
|
32 |
+
if not v.startswith("sk-"):
|
33 |
+
raise ValueError("Invalid OpenAI API key format")
|
34 |
+
return v
|
35 |
+
|
36 |
+
def __init__(self, **kwargs):
|
37 |
+
# Force reload of environment variables
|
38 |
+
load_dotenv(override=True)
|
39 |
+
super().__init__(**kwargs)
|
40 |
+
|
41 |
+
|
42 |
+
def get_settings():
|
43 |
+
return Settings()
|
core/__init__.py
ADDED
File without changes
|
core/document_processor.py
ADDED
File without changes
|
core/download_dataset.py
ADDED
@@ -0,0 +1,440 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import shutil
|
4 |
+
from langchain_openai import OpenAIEmbeddings
|
5 |
+
from langchain_text_splitters import RecursiveCharacterTextSplitter
|
6 |
+
import requests
|
7 |
+
import tarfile
|
8 |
+
from langchain.schema import Document
|
9 |
+
import hashlib
|
10 |
+
import xml.etree.ElementTree as ET
|
11 |
+
from urllib import request
|
12 |
+
from s3_utils import S3Handler
|
13 |
+
from config import get_settings
|
14 |
+
from PyPDF2 import PdfReader
|
15 |
+
|
16 |
+
|
17 |
+
|
18 |
+
|
19 |
+
class PubMedDownloader:
|
20 |
+
def __init__(self, s3_handler, pubmed_base_url, pinecone_index, embedding_model, from_date="2024-01-01", until_date="2024-11-01", limit=3):
|
21 |
+
self.s3_handler = s3_handler
|
22 |
+
self.settings = get_settings()
|
23 |
+
self.pubmed_base_url = pubmed_base_url
|
24 |
+
self.from_date = from_date
|
25 |
+
self.until_date = until_date
|
26 |
+
self.limit = limit
|
27 |
+
self.local_download_dir = "downloaded_pdfs"
|
28 |
+
os.makedirs(self.local_download_dir, exist_ok=True)
|
29 |
+
self.pinecone_index = pinecone_index # Pinecone index instance
|
30 |
+
self.embedding_model = embedding_model # Embedding model instance
|
31 |
+
|
32 |
+
def split_and_embed(self, documents, metadata_entry):
|
33 |
+
"""Split documents into chunks and embed them sequentially."""
|
34 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
35 |
+
chunk_size=self.settings.CHUNK_SIZE,
|
36 |
+
chunk_overlap=self.settings.CHUNK_OVERLAP
|
37 |
+
)
|
38 |
+
chunks = text_splitter.split_documents(documents)
|
39 |
+
print(f'total chunks created: {len(chunks)}')
|
40 |
+
batch_size = 50
|
41 |
+
pmc_id = metadata_entry['pmc_id']
|
42 |
+
for batch_index in range(0, len(chunks), batch_size):
|
43 |
+
batch = chunks[batch_index: batch_index + batch_size]
|
44 |
+
print(f'len of batch: {len(batch)}')
|
45 |
+
try:
|
46 |
+
# Process a single batch
|
47 |
+
|
48 |
+
# Create ids for the batch
|
49 |
+
# ids = [f"chunk_{batch_index}_{j}" for j in range(len(batch))]
|
50 |
+
ids = [f"{pmc_id}_chunk_{batch_index}_{j}" for j in range(len(batch))]
|
51 |
+
print(f'len of ids: {len(ids)}')
|
52 |
+
print(f'id sample: {ids[0]}')
|
53 |
+
# Get texts and generate embeddings
|
54 |
+
texts = [doc.page_content for doc in batch]
|
55 |
+
print(f'len of texts: {len(texts)}')
|
56 |
+
embeddings = self.embedding_model.embed_documents(texts)
|
57 |
+
|
58 |
+
|
59 |
+
metadata = []
|
60 |
+
for doc in batch:
|
61 |
+
chunk_metadata = metadata_entry.copy() # Copy base metadata
|
62 |
+
chunk_metadata["text"] = doc.page_content # Add chunk-specific text
|
63 |
+
metadata.append(chunk_metadata)
|
64 |
+
# Create upsert batch
|
65 |
+
to_upsert = list(zip(ids, embeddings, metadata))
|
66 |
+
|
67 |
+
# Upsert to Pinecone
|
68 |
+
self.pinecone_index.upsert(vectors=to_upsert)
|
69 |
+
print(f"Successfully upserted {len(to_upsert)} chunks to Pinecone.")
|
70 |
+
|
71 |
+
except Exception as e:
|
72 |
+
print(f"Error processing batch {batch_index}: {e}")
|
73 |
+
|
74 |
+
|
75 |
+
def fetch_records(self, resumption_token=None):
|
76 |
+
"""
|
77 |
+
Fetch records from PubMed using optional resumptionToken.
|
78 |
+
|
79 |
+
Args:
|
80 |
+
resumption_token (str, optional): Token to resume fetching records. Defaults to None.
|
81 |
+
|
82 |
+
Returns:
|
83 |
+
ElementTree.Element: Parsed XML root of the API response.
|
84 |
+
"""
|
85 |
+
# Build the base URL
|
86 |
+
url = f"{self.pubmed_base_url}"
|
87 |
+
|
88 |
+
# Define parameters
|
89 |
+
params = {
|
90 |
+
"format" : "tgz"
|
91 |
+
}
|
92 |
+
|
93 |
+
# Add date range if provided
|
94 |
+
if self.from_date and self.until_date:
|
95 |
+
params["from"] = self.from_date
|
96 |
+
params["until"] = self.until_date
|
97 |
+
|
98 |
+
# Add resumptionToken if available
|
99 |
+
if resumption_token:
|
100 |
+
params["resumptionToken"] = resumption_token
|
101 |
+
print(f"Using resumption token: {resumption_token}")
|
102 |
+
|
103 |
+
# Make the request
|
104 |
+
response = requests.get(url, params=params)
|
105 |
+
response.raise_for_status() # Raise an error for bad HTTP responses
|
106 |
+
|
107 |
+
# Parse and return the XML content
|
108 |
+
return ET.fromstring(response.content)
|
109 |
+
|
110 |
+
|
111 |
+
|
112 |
+
def save_metadata_to_s3(self, metadata, bucket, key):
|
113 |
+
print(f"Saving metadata to S3: s3://{bucket}/{key}")
|
114 |
+
self.s3_handler.upload_string_to_s3(metadata, bucket, key)
|
115 |
+
|
116 |
+
def save_pdf_to_s3(self, local_filename, bucket, s3_key):
|
117 |
+
"""Upload PDF to S3 and then delete the local file."""
|
118 |
+
print(f"Uploading PDF to S3: s3://{bucket}/{s3_key}")
|
119 |
+
self.s3_handler.upload_file_to_s3(local_filename, bucket, s3_key)
|
120 |
+
# Delete the local file after upload
|
121 |
+
if os.path.exists(local_filename):
|
122 |
+
os.remove(local_filename)
|
123 |
+
print(f"Deleted local file: {local_filename}")
|
124 |
+
else:
|
125 |
+
print(f"File not found for deletion: {local_filename}")
|
126 |
+
|
127 |
+
def update_metadata_and_upload(self, metadata_entry, bucket_name, metadata_file_key):
|
128 |
+
"""Update metadata list with a new entry and upload it to S3 as JSON."""
|
129 |
+
# Add new entry to metadata
|
130 |
+
|
131 |
+
# Convert metadata to JSON and upload to S3
|
132 |
+
metadata_json = json.dumps(metadata_entry, indent=4)
|
133 |
+
self.s3_handler.upload_string_to_s3(metadata_json, bucket_name, metadata_file_key)
|
134 |
+
print(f"Updated metadata uploaded to s3://{bucket_name}/{metadata_file_key}")
|
135 |
+
|
136 |
+
|
137 |
+
|
138 |
+
|
139 |
+
def download_and_process_tgz(self, ftp_link, pmc_id):
|
140 |
+
try:
|
141 |
+
metadata_entry = {}
|
142 |
+
|
143 |
+
# Step 1: Download TGZ
|
144 |
+
local_tgz_filename = os.path.join(self.local_download_dir, f"{pmc_id}.tgz")
|
145 |
+
print(f"Downloading TGZ: {ftp_link} saving in {local_tgz_filename}")
|
146 |
+
request.urlretrieve(ftp_link, local_tgz_filename)
|
147 |
+
|
148 |
+
# Step 2: Extract TGZ into a temporary directory
|
149 |
+
temp_extract_dir = os.path.join(self.local_download_dir, f"{pmc_id}_temp")
|
150 |
+
os.makedirs(temp_extract_dir, exist_ok=True)
|
151 |
+
print(f"Temporary extract dir: {temp_extract_dir}")
|
152 |
+
|
153 |
+
with tarfile.open(local_tgz_filename, "r:gz") as tar:
|
154 |
+
tar.extractall(path=temp_extract_dir)
|
155 |
+
|
156 |
+
# Step 3: Handle Nested Structure (Move Contents to Target Directory)
|
157 |
+
final_extract_dir = os.path.join(self.local_download_dir, pmc_id)
|
158 |
+
os.makedirs(final_extract_dir, exist_ok=True)
|
159 |
+
|
160 |
+
# Check if the archive creates a single root directory (e.g., PMC8419487/)
|
161 |
+
extracted_items = os.listdir(temp_extract_dir)
|
162 |
+
if len(extracted_items) == 1 and os.path.isdir(os.path.join(temp_extract_dir, extracted_items[0])):
|
163 |
+
# Move contents of the single folder to the final directory
|
164 |
+
nested_dir = os.path.join(temp_extract_dir, extracted_items[0])
|
165 |
+
for item in os.listdir(nested_dir):
|
166 |
+
shutil.move(os.path.join(nested_dir, item), final_extract_dir)
|
167 |
+
else:
|
168 |
+
# If no single root folder, move all files directly
|
169 |
+
for item in extracted_items:
|
170 |
+
shutil.move(os.path.join(temp_extract_dir, item), final_extract_dir)
|
171 |
+
|
172 |
+
print(f"Final extracted dir: {final_extract_dir}")
|
173 |
+
|
174 |
+
# Clean up the temporary extraction directory
|
175 |
+
shutil.rmtree(temp_extract_dir)
|
176 |
+
print(f"Temporary extract dir deleted: {temp_extract_dir}")
|
177 |
+
|
178 |
+
# Process the extracted files as before...
|
179 |
+
xml_file = [f for f in os.listdir(final_extract_dir) if f.endswith(".xml") or f.endswith(".nxml")]
|
180 |
+
pdf_path = [f for f in os.listdir(final_extract_dir) if f.endswith("pdf")]
|
181 |
+
|
182 |
+
if xml_file:
|
183 |
+
xml_path = os.path.join(final_extract_dir, xml_file[0])
|
184 |
+
metadata_entry = self.process_xml_metadata(xml_path, pmc_id)
|
185 |
+
else:
|
186 |
+
print(f"No XML file found in TGZ for PMCID: {pmc_id}")
|
187 |
+
print(f'Skipping article')
|
188 |
+
|
189 |
+
if pdf_path:
|
190 |
+
pdf_path = os.path.join(final_extract_dir, pdf_path[0])
|
191 |
+
document = self.download_and_process_pdf(pdf_path, pmc_id, self.settings.AWS_BUCKET_NAME)
|
192 |
+
else:
|
193 |
+
if metadata_entry.get('body_text') and metadata_entry['body_text'] != "N/A":
|
194 |
+
document = Document(
|
195 |
+
page_content=metadata_entry['body_text'], metadata=metadata_entry
|
196 |
+
)
|
197 |
+
metadata_entry.pop("body_text")
|
198 |
+
else:
|
199 |
+
print(f'Body content and PDF both not found, hence skipping this PDF')
|
200 |
+
document = None
|
201 |
+
|
202 |
+
# Cleanup: Remove the downloaded TGZ file
|
203 |
+
if os.path.exists(local_tgz_filename):
|
204 |
+
os.remove(local_tgz_filename)
|
205 |
+
print(f"Removed file: {local_tgz_filename}")
|
206 |
+
if os.path.exists(final_extract_dir):
|
207 |
+
shutil.rmtree(final_extract_dir)
|
208 |
+
|
209 |
+
return metadata_entry, document
|
210 |
+
|
211 |
+
except Exception as e:
|
212 |
+
print(f"Cannot download TGZ file for {pmc_id} : ftp link : {ftp_link}")
|
213 |
+
print(f"[ERROR] {str(e)}")
|
214 |
+
return {}, None
|
215 |
+
|
216 |
+
|
217 |
+
def extract_text_from_element(self, element):
|
218 |
+
"""
|
219 |
+
Recursively extract all text from an XML element and its children.
|
220 |
+
|
221 |
+
Args:
|
222 |
+
element (Element): XML element to extract text from.
|
223 |
+
|
224 |
+
Returns:
|
225 |
+
str: Concatenated text content of the element and its children.
|
226 |
+
"""
|
227 |
+
text_content = element.text or "" # Start with the element's own text
|
228 |
+
for child in element:
|
229 |
+
text_content += self.extract_text_from_element(child) # Recurse into children
|
230 |
+
if child.tail: # Include any tail text after the child element
|
231 |
+
text_content += child.tail
|
232 |
+
return text_content.strip()
|
233 |
+
|
234 |
+
def process_xml_metadata(self, xml_path, pmc_id):
|
235 |
+
tree = ET.parse(xml_path)
|
236 |
+
root = tree.getroot()
|
237 |
+
|
238 |
+
# Extract metadata
|
239 |
+
title_elem = root.find(".//article-title")
|
240 |
+
title = title_elem.text if title_elem is not None else "No Title Available"
|
241 |
+
|
242 |
+
# title = root.find(".//article-title").text if root.find(".//article-title") else "No Title Available"
|
243 |
+
# abstract = root.find(".//abstract/p").text if root.find(".//abstract/p") else "No Abstract Available"
|
244 |
+
|
245 |
+
# Abstract extraction
|
246 |
+
abstract_elem = root.find(".//abstract/p")
|
247 |
+
abstract = abstract_elem.text if abstract_elem is not None else "No Abstract Available"
|
248 |
+
|
249 |
+
|
250 |
+
# doi = root.find(".//article-id[@pub-id-type='doi']").text if root.find(".//article-id[@pub-id-type='doi']") else "N/A"
|
251 |
+
# DOI extraction
|
252 |
+
doi_elem = root.find(".//article-id[@pub-id-type='doi']")
|
253 |
+
doi = doi_elem.text if doi_elem is not None else "N/A"
|
254 |
+
|
255 |
+
|
256 |
+
# authors = [f"{author.find('surname').text}, {author.find('given-names').text}"
|
257 |
+
# for author in root.findall(".//contrib/name")]
|
258 |
+
|
259 |
+
authors = []
|
260 |
+
for author in root.findall(".//contrib/name"):
|
261 |
+
surname = author.find('surname')
|
262 |
+
given_names = author.find('given-names')
|
263 |
+
# Safely handle missing elements
|
264 |
+
surname_text = surname.text if surname is not None else "Unknown Surname"
|
265 |
+
given_names_text = given_names.text if given_names is not None else "Unknown Given Names"
|
266 |
+
authors.append(f"{surname_text}, {given_names_text}")
|
267 |
+
|
268 |
+
|
269 |
+
|
270 |
+
keywords = [kw.text for kw in root.findall(".//kwd")]
|
271 |
+
|
272 |
+
# Extract publication date
|
273 |
+
pub_date_node = root.find(".//pub-date")
|
274 |
+
if pub_date_node is not None:
|
275 |
+
month = pub_date_node.find("month").text if pub_date_node.find("month") is not None else "N/A"
|
276 |
+
year = pub_date_node.find("year").text if pub_date_node.find("year") is not None else "N/A"
|
277 |
+
pub_type = pub_date_node.attrib.get("pub-type", "N/A")
|
278 |
+
publication_date = f"{year}-{month}" if month != "N/A" else year
|
279 |
+
else:
|
280 |
+
publication_date = "N/A"
|
281 |
+
|
282 |
+
# Extract text content from <body>
|
283 |
+
body_node = root.find(".//body")
|
284 |
+
body_text = ""
|
285 |
+
if body_node is not None:
|
286 |
+
body_text = self.extract_text_from_element(body_node)
|
287 |
+
else:
|
288 |
+
body_text = "N/A"
|
289 |
+
|
290 |
+
# Save enriched metadata
|
291 |
+
metadata_entry = {
|
292 |
+
"pmc_id": pmc_id,
|
293 |
+
"title": title,
|
294 |
+
"abstract": abstract,
|
295 |
+
"authors": authors,
|
296 |
+
"keywords": keywords,
|
297 |
+
"doi": doi,
|
298 |
+
"source": f"https://pmc.ncbi.nlm.nih.gov/articles/{pmc_id}",
|
299 |
+
"publication_date" : publication_date,
|
300 |
+
"body_text" : body_text
|
301 |
+
}
|
302 |
+
return metadata_entry
|
303 |
+
|
304 |
+
|
305 |
+
def download_and_process_pdf(self, pdf_path, pmc_id, bucket_name):
|
306 |
+
try:
|
307 |
+
|
308 |
+
pdf_reader = PdfReader(pdf_path)
|
309 |
+
text = "".join(page.extract_text() for page in pdf_reader.pages)
|
310 |
+
|
311 |
+
# Create document object
|
312 |
+
document = Document(
|
313 |
+
page_content=text,
|
314 |
+
metadata={"source": f"s3://{bucket_name}/{pmc_id}.pdf"}
|
315 |
+
)
|
316 |
+
|
317 |
+
return document
|
318 |
+
except Exception as e:
|
319 |
+
print(f"Error processing PDF for {pmc_id}: {e}")
|
320 |
+
return None
|
321 |
+
|
322 |
+
def process_and_save(self, bucket_name, metadata_file_key):
|
323 |
+
# Load existing metadata from S3
|
324 |
+
try:
|
325 |
+
metadata_content = self.s3_handler.download_string_from_s3(bucket_name, metadata_file_key)
|
326 |
+
existing_metadata = json.loads(metadata_content)
|
327 |
+
existing_ids = {record["pmc_id"] for record in existing_metadata}
|
328 |
+
print(f"Found {len(existing_ids)} existing records in metadata.")
|
329 |
+
except Exception as e:
|
330 |
+
# If metadata file doesn't exist or is empty, initialize an empty list
|
331 |
+
print(f"Could not load metadata: {e}. Assuming no existing records.")
|
332 |
+
existing_metadata = []
|
333 |
+
existing_ids = set()
|
334 |
+
resumption_token = None
|
335 |
+
|
336 |
+
while True:
|
337 |
+
root = self.fetch_records(resumption_token=resumption_token)
|
338 |
+
print(f'len of records: {len(root.findall(".//record"))}')
|
339 |
+
resumption = root.find(".//resumption")
|
340 |
+
print(f'resumption token: {resumption}')
|
341 |
+
for record in root.findall(".//record"):
|
342 |
+
# print(f'first record: ')
|
343 |
+
|
344 |
+
pmc_id = record.attrib.get("id")
|
345 |
+
# print(f'[INFO] pmc id : {pmc_id}')
|
346 |
+
|
347 |
+
if pmc_id in existing_ids:
|
348 |
+
# print(f"Skipping already downloaded record: {pmc_id}")
|
349 |
+
continue
|
350 |
+
|
351 |
+
pdf_link = None
|
352 |
+
ftp_link = None
|
353 |
+
|
354 |
+
for link in record.findall("link"):
|
355 |
+
if link.attrib.get("format") == "tgz":
|
356 |
+
ftp_link = link.attrib.get("href")
|
357 |
+
if link.attrib.get("format") == "pdf":
|
358 |
+
pdf_link = link.attrib.get("href")
|
359 |
+
|
360 |
+
print(f'[INFO] links found: pdf {pdf_link} and ftp {ftp_link}')
|
361 |
+
metadata = { }
|
362 |
+
|
363 |
+
# Process `tgz` first if available
|
364 |
+
if ftp_link:
|
365 |
+
metadata, document = self.download_and_process_tgz(ftp_link, pmc_id)
|
366 |
+
# documents.append(document)
|
367 |
+
if not document:
|
368 |
+
# print(f'this document doesnt have content. continue .. ')
|
369 |
+
continue
|
370 |
+
|
371 |
+
|
372 |
+
self.split_and_embed([document], metadata)
|
373 |
+
|
374 |
+
# Create document object
|
375 |
+
|
376 |
+
existing_metadata.append(metadata)
|
377 |
+
self.update_metadata_and_upload(existing_metadata, bucket_name , metadata_file_key)
|
378 |
+
resumption = root.find(".//resumption")
|
379 |
+
if resumption is not None:
|
380 |
+
link = resumption.find("link")
|
381 |
+
if link is not None:
|
382 |
+
resumption_token = link.attrib.get("token", "").strip()
|
383 |
+
if not resumption_token:
|
384 |
+
print("No more tokens found, stopping pagination.")
|
385 |
+
break
|
386 |
+
else:
|
387 |
+
print("No link found, stopping pagination.")
|
388 |
+
break
|
389 |
+
else:
|
390 |
+
print("No resumption element, stopping pagination.")
|
391 |
+
break
|
392 |
+
|
393 |
+
|
394 |
+
def create_or_connect_index(index_name, dimension):
|
395 |
+
pc = pinecone.Pinecone(settings.PINECONE_API_KEY)
|
396 |
+
"""Create or connect to existing Pinecone index"""
|
397 |
+
spec = pinecone.ServerlessSpec(
|
398 |
+
cloud=settings.CLOUD,
|
399 |
+
region=settings.REGION
|
400 |
+
)
|
401 |
+
print(f'all indexes: {pc.list_indexes()}')
|
402 |
+
|
403 |
+
if index_name not in pc.list_indexes().names():
|
404 |
+
pc.create_index(
|
405 |
+
name=index_name,
|
406 |
+
dimension=dimension,
|
407 |
+
metric='cosine', # You can use 'dotproduct' or other metrics if needed
|
408 |
+
spec=spec
|
409 |
+
)
|
410 |
+
return pc.Index(settings.INDEX_NAME)
|
411 |
+
if __name__ == "__main__":
|
412 |
+
"""
|
413 |
+
#todo: add all args as argument parser
|
414 |
+
#todo: like from and until date, and all variables
|
415 |
+
#todo: add one variable like how many iterations we need to go
|
416 |
+
"""
|
417 |
+
# Load settings
|
418 |
+
settings = get_settings()
|
419 |
+
|
420 |
+
# Initialize S3 handler
|
421 |
+
s3_handler = S3Handler()
|
422 |
+
import pinecone
|
423 |
+
pc_index = create_or_connect_index(settings.INDEX_NAME, settings.DIMENSIONS)
|
424 |
+
|
425 |
+
|
426 |
+
|
427 |
+
|
428 |
+
# Create the downloader instance
|
429 |
+
downloader = PubMedDownloader(
|
430 |
+
s3_handler=s3_handler,
|
431 |
+
pubmed_base_url=settings.PUBMED_BASE_URL,
|
432 |
+
pinecone_index= pc_index,
|
433 |
+
embedding_model=OpenAIEmbeddings(openai_api_key=settings.OPENAI_API_KEY)
|
434 |
+
)
|
435 |
+
|
436 |
+
# Process and save
|
437 |
+
downloader.process_and_save(
|
438 |
+
bucket_name=settings.AWS_BUCKET_NAME,
|
439 |
+
metadata_file_key="pubmed_metadata/metadata.json"
|
440 |
+
)
|
core/pineconeqa.py
ADDED
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# qa_system.py
|
2 |
+
from langchain.vectorstores import Pinecone
|
3 |
+
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
|
4 |
+
import pinecone
|
5 |
+
from langchain_pinecone import PineconeVectorStore
|
6 |
+
from langchain.chains import create_retrieval_chain
|
7 |
+
from langchain.chains.combine_documents import create_stuff_documents_chain
|
8 |
+
from langchain_core.prompts import ChatPromptTemplate
|
9 |
+
|
10 |
+
class PineconeQA:
|
11 |
+
def __init__(self, pinecone_api_key, openai_api_key, index_name):
|
12 |
+
# Initialize Pinecone
|
13 |
+
self.pc = pinecone.Pinecone(api_key=pinecone_api_key)
|
14 |
+
self.index = self.pc.Index(index_name)
|
15 |
+
|
16 |
+
# Initialize embeddings
|
17 |
+
self.embeddings = OpenAIEmbeddings(
|
18 |
+
openai_api_key=openai_api_key
|
19 |
+
)
|
20 |
+
|
21 |
+
# Create retriever
|
22 |
+
self.retriever = PineconeVectorStore(
|
23 |
+
index=self.index,
|
24 |
+
embedding=self.embeddings
|
25 |
+
)
|
26 |
+
|
27 |
+
# Initialize LLM
|
28 |
+
self.llm = ChatOpenAI(
|
29 |
+
openai_api_key=openai_api_key,
|
30 |
+
model="gpt-4o",
|
31 |
+
temperature=0.2
|
32 |
+
)
|
33 |
+
|
34 |
+
# Create the RAG chain
|
35 |
+
self._create_rag_chain()
|
36 |
+
|
37 |
+
def _create_rag_chain(self):
|
38 |
+
# Define system prompt
|
39 |
+
# system_prompt = (
|
40 |
+
# "You are an assistant for question-answering tasks. "
|
41 |
+
# "Use the following pieces of retrieved context to answer "
|
42 |
+
# "the question. If you don't know the answer, say that you "
|
43 |
+
# "don't know. Use three sentences maximum and keep the "
|
44 |
+
# "answer concise."
|
45 |
+
# "\n\n"
|
46 |
+
# "{context}"
|
47 |
+
# )
|
48 |
+
|
49 |
+
|
50 |
+
system_prompt = (
|
51 |
+
"You are an expert assistant for biomedical question-answering tasks. "
|
52 |
+
"You will be provided with context retrieved from medical literature."
|
53 |
+
"The medical literature is all from PubMed Open Access Articles. "
|
54 |
+
"Use this context to answer the question as accurately as possible. "
|
55 |
+
"The response might not be added precisly, so try to derive the answers from it as much as possible."
|
56 |
+
"If the context does not contain the required information, explain why. "
|
57 |
+
"Provide a concise and accurate answer "
|
58 |
+
"\n\n"
|
59 |
+
"Context:\n{context}\n"
|
60 |
+
)
|
61 |
+
# Create chat prompt template
|
62 |
+
prompt = ChatPromptTemplate.from_messages([
|
63 |
+
("system", system_prompt),
|
64 |
+
("human", "{input}"),
|
65 |
+
])
|
66 |
+
|
67 |
+
# Create question-answer chain
|
68 |
+
question_answer_chain = create_stuff_documents_chain(
|
69 |
+
self.llm,
|
70 |
+
prompt
|
71 |
+
)
|
72 |
+
|
73 |
+
# Create the RAG chain
|
74 |
+
self.rag_chain = create_retrieval_chain(
|
75 |
+
self.retriever.as_retriever(search_type="mmr"),
|
76 |
+
question_answer_chain
|
77 |
+
)
|
78 |
+
def merge_relevant_chunks(self, retrieved_docs, question, max_tokens=1500):
|
79 |
+
"""
|
80 |
+
Merge document chunks based on their semantic relevance to the question.
|
81 |
+
"""
|
82 |
+
merged_context = ""
|
83 |
+
current_tokens = 0
|
84 |
+
|
85 |
+
for doc in retrieved_docs:
|
86 |
+
tokens = doc.page_content.split()
|
87 |
+
if current_tokens + len(tokens) <= max_tokens:
|
88 |
+
merged_context += doc.page_content + "\n"
|
89 |
+
current_tokens += len(tokens)
|
90 |
+
else:
|
91 |
+
break
|
92 |
+
|
93 |
+
return merged_context
|
94 |
+
|
95 |
+
|
96 |
+
def ask(self, question):
|
97 |
+
"""
|
98 |
+
Ask a question and get response with sources
|
99 |
+
"""
|
100 |
+
# Initialize conversation history if it doesn't exist
|
101 |
+
if not hasattr(self, "conversation_history"):
|
102 |
+
self.conversation_history = []
|
103 |
+
|
104 |
+
try:
|
105 |
+
|
106 |
+
system_prompt = (
|
107 |
+
"You are an expert assistant for biomedical question-answering tasks. "
|
108 |
+
"You will be provided with context retrieved from medical literature, specifically PubMed Open Access Articles. "
|
109 |
+
"Use the provided context to directly answer the question in the most accurate and concise manner possible. "
|
110 |
+
"If the context does not provide sufficient information, state that the specific details are not available in the context."
|
111 |
+
"Do not include statements about limitations of the context in your response. "
|
112 |
+
"Your answer should sound authoritative and professional, tailored for a medical audience."
|
113 |
+
"\n\n"
|
114 |
+
"Context:\n{context}\n"
|
115 |
+
)
|
116 |
+
# Create chat prompt template
|
117 |
+
prompt = ChatPromptTemplate.from_messages([
|
118 |
+
("system", system_prompt),
|
119 |
+
("human", "{input}"),
|
120 |
+
])
|
121 |
+
|
122 |
+
# Create question-answer chain
|
123 |
+
question_answer_chain = create_stuff_documents_chain(
|
124 |
+
self.llm,
|
125 |
+
prompt
|
126 |
+
)
|
127 |
+
|
128 |
+
|
129 |
+
|
130 |
+
results = create_retrieval_chain(
|
131 |
+
self.retriever.as_retriever(seach_type="mmr"),
|
132 |
+
question_answer_chain
|
133 |
+
).invoke({"input": question})
|
134 |
+
|
135 |
+
return {
|
136 |
+
"answer": results["answer"],
|
137 |
+
"context": results["context"]
|
138 |
+
}
|
139 |
+
except Exception as e:
|
140 |
+
return {
|
141 |
+
"error": str(e)
|
142 |
+
}
|
core/rag_engine.py
ADDED
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import sys
|
3 |
+
import os
|
4 |
+
|
5 |
+
import boto3
|
6 |
+
import hashlib
|
7 |
+
import json
|
8 |
+
import threading
|
9 |
+
# Add the project root directory to Python path
|
10 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
11 |
+
from typing import List
|
12 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
13 |
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
14 |
+
from langchain_openai import OpenAIEmbeddings
|
15 |
+
import pinecone
|
16 |
+
from tqdm.auto import tqdm
|
17 |
+
from langchain.schema import Document
|
18 |
+
from config import get_settings
|
19 |
+
from dotenv import load_dotenv
|
20 |
+
from io import BytesIO
|
21 |
+
from PyPDF2 import PdfReader
|
22 |
+
load_dotenv()
|
23 |
+
|
24 |
+
class RAGPrep:
|
25 |
+
def __init__(self, processed_hashes_file="processed_hashes.json"):
|
26 |
+
self.settings = get_settings()
|
27 |
+
self.index_name = self.settings.INDEX_NAME
|
28 |
+
self.pc = self.init_pinecone()
|
29 |
+
self.embeddings = OpenAIEmbeddings(openai_api_key=self.settings.OPENAI_API_KEY)
|
30 |
+
self.processed_hashes_file = processed_hashes_file
|
31 |
+
self.processed_hashes = self.load_processed_hashes()
|
32 |
+
|
33 |
+
def init_pinecone(self):
|
34 |
+
"""Initialize Pinecone client"""
|
35 |
+
pc = pinecone.Pinecone(self.settings.PINECONE_API_KEY)
|
36 |
+
return pc
|
37 |
+
|
38 |
+
# Define function to create or connect to an existing index
|
39 |
+
def create_or_connect_index(self,index_name, dimension):
|
40 |
+
"""Create or connect to existing Pinecone index"""
|
41 |
+
spec = pinecone.ServerlessSpec(
|
42 |
+
cloud=self.settings.CLOUD,
|
43 |
+
region=self.settings.REGION
|
44 |
+
)
|
45 |
+
print(f'all indexes: {self.pc.list_indexes()}')
|
46 |
+
|
47 |
+
if index_name not in self.pc.list_indexes().names():
|
48 |
+
self.pc.create_index(
|
49 |
+
name=index_name,
|
50 |
+
dimension=dimension,
|
51 |
+
metric='cosine', # You can use 'dotproduct' or other metrics if needed
|
52 |
+
spec=spec
|
53 |
+
)
|
54 |
+
return self.pc.Index(index_name)
|
55 |
+
|
56 |
+
|
57 |
+
def load_processed_hashes(self):
|
58 |
+
"""Load previously processed hashes from a file."""
|
59 |
+
if os.path.exists(self.processed_hashes_file):
|
60 |
+
with open(self.processed_hashes_file, "r") as f:
|
61 |
+
return set(json.load(f))
|
62 |
+
return set()
|
63 |
+
|
64 |
+
def save_processed_hashes(self):
|
65 |
+
"""Save processed hashes to a file."""
|
66 |
+
with open(self.processed_hashes_file, "w") as f:
|
67 |
+
json.dump(list(self.processed_hashes), f)
|
68 |
+
|
69 |
+
def generate_pdf_hash(self, pdf_content: bytes):
|
70 |
+
"""Generate a hash for the given PDF content."""
|
71 |
+
hasher = hashlib.md5()
|
72 |
+
hasher.update(pdf_content)
|
73 |
+
return hasher.hexdigest()
|
74 |
+
|
75 |
+
def load_and_split_pdfs(self, chunk_from = 50, chunk_to = 100) -> List[Document]:
|
76 |
+
"""Load PDFs from S3, extract text, and split into chunks."""
|
77 |
+
print("***********")
|
78 |
+
# Initialize S3 client
|
79 |
+
s3_client = boto3.client(
|
80 |
+
's3',
|
81 |
+
aws_access_key_id=self.settings.AWS_ACCESS_KEY,
|
82 |
+
aws_secret_access_key=self.settings.AWS_SECRET_KEY,
|
83 |
+
region_name=self.settings.AWS_REGION
|
84 |
+
)
|
85 |
+
|
86 |
+
# List all PDF files in the S3 bucket and prefix
|
87 |
+
print(f"Listing files in S3 bucket: {self.settings.AWS_BUCKET_NAME}")
|
88 |
+
response = s3_client.list_objects_v2(Bucket=self.settings.AWS_BUCKET_NAME, Prefix="")
|
89 |
+
s3_keys = [obj['Key'] for obj in response.get('Contents', [])]
|
90 |
+
|
91 |
+
print(f"Found {len(s3_keys)} PDF files in S3")
|
92 |
+
documents = []
|
93 |
+
|
94 |
+
# Process each PDF file
|
95 |
+
for s3_key in s3_keys[chunk_from:chunk_to]:
|
96 |
+
print(f"Processing file: {s3_key}")
|
97 |
+
if not s3_key.lower().endswith(".pdf"):
|
98 |
+
print("Not a PDF file, skipping.")
|
99 |
+
continue
|
100 |
+
|
101 |
+
try:
|
102 |
+
# Read file from S3
|
103 |
+
obj = s3_client.get_object(Bucket=self.settings.AWS_BUCKET_NAME, Key=s3_key)
|
104 |
+
pdf_content = obj['Body'].read()
|
105 |
+
|
106 |
+
# Generate hash and check for duplicates
|
107 |
+
pdf_hash = self.generate_pdf_hash(pdf_content)
|
108 |
+
if pdf_hash in self.processed_hashes:
|
109 |
+
print(f"Duplicate PDF detected: {s3_key}, skipping.")
|
110 |
+
continue
|
111 |
+
|
112 |
+
# Extract text from PDF
|
113 |
+
pdf_file = BytesIO(pdf_content)
|
114 |
+
pdf_reader = PdfReader(pdf_file)
|
115 |
+
text = "".join(page.extract_text() for page in pdf_reader.pages)
|
116 |
+
|
117 |
+
# Add document with metadata
|
118 |
+
documents.append(Document(page_content=text, metadata={"source": s3_key}))
|
119 |
+
self.processed_hashes.add(pdf_hash)
|
120 |
+
|
121 |
+
except Exception as e:
|
122 |
+
print(f"Error processing {s3_key}: {e}")
|
123 |
+
|
124 |
+
print(f"Extracted text from {len(documents)} documents")
|
125 |
+
|
126 |
+
# Split documents into chunks
|
127 |
+
text_splitter = RecursiveCharacterTextSplitter(
|
128 |
+
chunk_size=self.settings.CHUNK_SIZE,
|
129 |
+
chunk_overlap=self.settings.CHUNK_OVERLAP
|
130 |
+
)
|
131 |
+
chunks = text_splitter.split_documents(documents)
|
132 |
+
print(f"Created {len(chunks)} chunks")
|
133 |
+
|
134 |
+
# Save updated hashes
|
135 |
+
self.save_processed_hashes()
|
136 |
+
return chunks
|
137 |
+
|
138 |
+
def process_and_upload(self, total_batch=200):
|
139 |
+
"""Process PDFs and upload to Pinecone"""
|
140 |
+
# Create or connect to index
|
141 |
+
index = self.create_or_connect_index(self.index_name, self.settings.DIMENSIONS)
|
142 |
+
|
143 |
+
# Load and split documents
|
144 |
+
print(f'//////// chunking: ////////')
|
145 |
+
current_batch = 0
|
146 |
+
for i in range(0, total_batch, 50):
|
147 |
+
batch_size = 50 # Adjust based on your needs
|
148 |
+
|
149 |
+
chunks = self.load_and_split_pdfs(current_batch, current_batch+batch_size)
|
150 |
+
current_batch = current_batch + batch_size
|
151 |
+
# Prepare for batch processing
|
152 |
+
max_threads = 4 # Adjust based on your hardware
|
153 |
+
|
154 |
+
def process_batch(batch, batch_index):
|
155 |
+
"""Process a single batch of chunks"""
|
156 |
+
print(f"Processing batch {batch_index} on thread: {threading.current_thread().name}")
|
157 |
+
print(f"Active threads: {threading.active_count()}")
|
158 |
+
# Create ids for batch
|
159 |
+
ids = [f"chunk_{batch_index}_{j}" for j in range(len(batch))]
|
160 |
+
|
161 |
+
# Get texts and generate embeddings
|
162 |
+
texts = [doc.page_content for doc in batch]
|
163 |
+
embeddings = self.embeddings.embed_documents(texts)
|
164 |
+
|
165 |
+
# Create metadata
|
166 |
+
metadata = [
|
167 |
+
{
|
168 |
+
"text": doc.page_content,
|
169 |
+
"source": doc.metadata.get("source", "unknown"),
|
170 |
+
"page": doc.metadata.get("page", 0)
|
171 |
+
}
|
172 |
+
for doc in batch
|
173 |
+
]
|
174 |
+
|
175 |
+
# Create upsert batch
|
176 |
+
return list(zip(ids, embeddings, metadata))
|
177 |
+
|
178 |
+
with ThreadPoolExecutor(max_threads) as executor:
|
179 |
+
futures = []
|
180 |
+
print(f"Batch size being used: {batch_size}")
|
181 |
+
|
182 |
+
for i in range(0, len(chunks), batch_size):
|
183 |
+
|
184 |
+
batch = chunks[i:i + batch_size]
|
185 |
+
futures.append(executor.submit(process_batch, batch, i))
|
186 |
+
|
187 |
+
# Gather results and upsert to Pinecone
|
188 |
+
for future in tqdm(as_completed(futures), total=len(futures), desc="Uploading batches"):
|
189 |
+
try:
|
190 |
+
to_upsert = future.result()
|
191 |
+
index.upsert(vectors=to_upsert)
|
192 |
+
except Exception as e:
|
193 |
+
print(f"Error processing batch: {e}")
|
194 |
+
|
195 |
+
print(f"Successfully processed and uploaded {len(chunks)} chunks to Pinecone")
|
196 |
+
|
197 |
+
|
198 |
+
def cleanup_index(self) -> bool:
|
199 |
+
"""
|
200 |
+
Delete all vectors from the Pinecone index.
|
201 |
+
|
202 |
+
Returns:
|
203 |
+
bool: True if cleanup was successful, False otherwise
|
204 |
+
|
205 |
+
Raises:
|
206 |
+
Exception: Logs any unexpected errors during cleanup
|
207 |
+
"""
|
208 |
+
try:
|
209 |
+
# Try to get the index
|
210 |
+
if self.index_name in self.pc.list_indexes().names():
|
211 |
+
print(f'index name found in {self.pc.list_indexes().names()}')
|
212 |
+
# Attempt to delete all vectors
|
213 |
+
index = self.pc.Index(self.index_name)
|
214 |
+
|
215 |
+
index.delete(delete_all=True)
|
216 |
+
|
217 |
+
print(f"Successfully cleaned up index: {self.index_name}")
|
218 |
+
return True
|
219 |
+
print(f'Index doesn\'t exist.')
|
220 |
+
return True
|
221 |
+
|
222 |
+
|
223 |
+
except Exception as e:
|
224 |
+
print(f"Unexpected error during index cleanup: {str(e)}")
|
225 |
+
# You might want to log this error as well
|
226 |
+
import logging
|
227 |
+
logging.error(f"Failed to cleanup index {self.index_name}. Error: {str(e)}")
|
228 |
+
return False
|
229 |
+
|
230 |
+
finally:
|
231 |
+
# Any cleanup code that should run regardless of success/failure
|
232 |
+
print("Cleanup operation completed.")
|
233 |
+
|
234 |
+
|
235 |
+
|
236 |
+
# Example usage:
|
237 |
+
if __name__ == "__main__":
|
238 |
+
# Example .env file content:
|
239 |
+
|
240 |
+
rag_prep = RAGPrep()
|
241 |
+
rag_prep.process_and_upload()
|
242 |
+
# rag_prep.cleanup_index()
|
core/s3_utils.py
ADDED
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
# Add the project root directory to Python path
|
4 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
5 |
+
import boto3
|
6 |
+
from botocore.exceptions import NoCredentialsError, ClientError
|
7 |
+
|
8 |
+
|
9 |
+
from config import get_settings
|
10 |
+
|
11 |
+
|
12 |
+
|
13 |
+
|
14 |
+
settings = get_settings()
|
15 |
+
|
16 |
+
|
17 |
+
|
18 |
+
class S3Handler:
|
19 |
+
def __init__(self):
|
20 |
+
self.s3_client = boto3.client(
|
21 |
+
's3',
|
22 |
+
aws_access_key_id=settings.AWS_ACCESS_KEY,
|
23 |
+
aws_secret_access_key=settings.AWS_SECRET_KEY,
|
24 |
+
region_name=settings.AWS_REGION
|
25 |
+
)
|
26 |
+
|
27 |
+
|
28 |
+
def upload_directory(self, local_directory, bucket_name, s3_prefix=""):
|
29 |
+
uploaded_files = []
|
30 |
+
errors = []
|
31 |
+
for root, _, files in os.walk(local_directory):
|
32 |
+
for filename in files:
|
33 |
+
# Get the full local path
|
34 |
+
local_path = os.path.join(root, filename)
|
35 |
+
|
36 |
+
# Get relative path by removing the local_directory prefix
|
37 |
+
relative_path = os.path.relpath(local_path, local_directory)
|
38 |
+
|
39 |
+
# Create S3 key (preserve directory structure)
|
40 |
+
s3_key = os.path.join(s3_prefix, relative_path).replace("\\", "/")
|
41 |
+
|
42 |
+
try:
|
43 |
+
print(f"Uploading {local_path} to {bucket_name}/{s3_key}")
|
44 |
+
self.s3_client.upload_file(local_path, bucket_name, s3_key)
|
45 |
+
uploaded_files.append(s3_key)
|
46 |
+
except ClientError as e:
|
47 |
+
print(f"Error uploading {local_path}: {str(e)}")
|
48 |
+
errors.append(local_path)
|
49 |
+
return uploaded_files, errors
|
50 |
+
|
51 |
+
def upload_file_to_s3(self, file_path, bucket_name, s3_key):
|
52 |
+
"""
|
53 |
+
Upload a single file to S3.
|
54 |
+
|
55 |
+
Args:
|
56 |
+
file_path (str): Local path to the file to upload.
|
57 |
+
bucket_name (str): Name of the S3 bucket.
|
58 |
+
s3_key (str): Key (path) to save the file in S3.
|
59 |
+
|
60 |
+
Returns:
|
61 |
+
str: The URL of the uploaded file.
|
62 |
+
"""
|
63 |
+
try:
|
64 |
+
self.s3_client.upload_file(file_path, bucket_name, s3_key)
|
65 |
+
print(f"Uploaded {file_path} to s3://{bucket_name}/{s3_key}")
|
66 |
+
return f"s3://{bucket_name}/{s3_key}"
|
67 |
+
except FileNotFoundError:
|
68 |
+
print(f"File not found: {file_path}")
|
69 |
+
raise
|
70 |
+
except NoCredentialsError:
|
71 |
+
print("AWS credentials not found.")
|
72 |
+
raise
|
73 |
+
except ClientError as e:
|
74 |
+
print(f"Error uploading file: {e}")
|
75 |
+
raise
|
76 |
+
def list_files(self, bucket_name, prefix=""):
|
77 |
+
"""List all files in the bucket with given prefix"""
|
78 |
+
try:
|
79 |
+
response = self.s3_client.list_objects_v2(
|
80 |
+
Bucket=bucket_name,
|
81 |
+
Prefix=prefix
|
82 |
+
)
|
83 |
+
|
84 |
+
print(f"\nFiles in bucket '{bucket_name}' with prefix '{prefix}':")
|
85 |
+
if 'Contents' in response:
|
86 |
+
for obj in response['Contents']:
|
87 |
+
print(f"- {obj['Key']} ({obj['Size']} bytes)")
|
88 |
+
return [obj['Key'] for obj in response['Contents']]
|
89 |
+
else:
|
90 |
+
print("No files found")
|
91 |
+
return []
|
92 |
+
|
93 |
+
except ClientError as e:
|
94 |
+
print(f"Error listing files: {str(e)}")
|
95 |
+
return []
|
96 |
+
|
97 |
+
def delete_all_files(self, bucket_name, prefix=""):
|
98 |
+
"""
|
99 |
+
Delete all files in the bucket with given prefix
|
100 |
+
|
101 |
+
Args:
|
102 |
+
bucket_name: Name of the S3 bucket
|
103 |
+
prefix: Optional prefix to delete only files under this path
|
104 |
+
|
105 |
+
Returns:
|
106 |
+
tuple: (number of deleted files, list of any files that failed to delete)
|
107 |
+
"""
|
108 |
+
deleted_count = 0
|
109 |
+
failed_deletes = []
|
110 |
+
|
111 |
+
try:
|
112 |
+
# List all objects in the bucket
|
113 |
+
while True:
|
114 |
+
# Get batch of objects
|
115 |
+
response = self.s3_client.list_objects_v2(
|
116 |
+
Bucket=bucket_name,
|
117 |
+
Prefix=prefix
|
118 |
+
)
|
119 |
+
|
120 |
+
# If bucket is empty
|
121 |
+
if 'Contents' not in response:
|
122 |
+
print(f"No files found in bucket '{bucket_name}' with prefix '{prefix}'")
|
123 |
+
break
|
124 |
+
|
125 |
+
# Prepare objects for deletion
|
126 |
+
objects_to_delete = [{'Key': obj['Key']} for obj in response['Contents']]
|
127 |
+
|
128 |
+
# Delete the batch of objects
|
129 |
+
delete_response = self.s3_client.delete_objects(
|
130 |
+
Bucket=bucket_name,
|
131 |
+
Delete={
|
132 |
+
'Objects': objects_to_delete,
|
133 |
+
'Quiet': False
|
134 |
+
}
|
135 |
+
)
|
136 |
+
|
137 |
+
# Count successful deletes
|
138 |
+
if 'Deleted' in delete_response:
|
139 |
+
deleted_count += len(delete_response['Deleted'])
|
140 |
+
for obj in delete_response['Deleted']:
|
141 |
+
print(f"Deleted: {obj['Key']}")
|
142 |
+
|
143 |
+
# Track failed deletes
|
144 |
+
if 'Errors' in delete_response:
|
145 |
+
for error in delete_response['Errors']:
|
146 |
+
failed_deletes.append(error['Key'])
|
147 |
+
print(f"Failed to delete {error['Key']}: {error['Message']}")
|
148 |
+
|
149 |
+
# Check if there are more objects to delete
|
150 |
+
if not response.get('IsTruncated'): # No more files
|
151 |
+
break
|
152 |
+
|
153 |
+
print(f"\nSuccessfully deleted {deleted_count} files")
|
154 |
+
if failed_deletes:
|
155 |
+
print(f"Failed to delete {len(failed_deletes)} files")
|
156 |
+
|
157 |
+
return deleted_count, failed_deletes
|
158 |
+
|
159 |
+
except ClientError as e:
|
160 |
+
print(f"Error deleting files: {str(e)}")
|
161 |
+
return 0, []
|
162 |
+
def upload_string_to_s3(self, string_data, bucket_name, s3_key):
|
163 |
+
"""
|
164 |
+
Upload a string as an object to S3.
|
165 |
+
|
166 |
+
Args:
|
167 |
+
string_data (str): The string content to upload.
|
168 |
+
bucket_name (str): The S3 bucket name.
|
169 |
+
s3_key (str): The S3 key (path) to save the file.
|
170 |
+
"""
|
171 |
+
try:
|
172 |
+
# Convert string data to bytes
|
173 |
+
self.s3_client.put_object(Body=string_data, Bucket=bucket_name, Key=s3_key)
|
174 |
+
print(f"Uploaded string to s3://{bucket_name}/{s3_key}")
|
175 |
+
except (NoCredentialsError, ClientError) as e:
|
176 |
+
print(f"Failed to upload string data: {e}")
|
177 |
+
raise
|
178 |
+
|
179 |
+
|
180 |
+
def download_string_from_s3(self, bucket_name, s3_key):
|
181 |
+
"""
|
182 |
+
Download a string object from S3 and return it.
|
183 |
+
|
184 |
+
Args:
|
185 |
+
bucket_name (str): The S3 bucket name.
|
186 |
+
s3_key (str): The S3 key (path) to the object.
|
187 |
+
|
188 |
+
Returns:
|
189 |
+
str: The content of the object as a string.
|
190 |
+
"""
|
191 |
+
try:
|
192 |
+
response = self.s3_client.get_object(Bucket=bucket_name, Key=s3_key)
|
193 |
+
content = response['Body'].read().decode('utf-8')
|
194 |
+
print(f"Downloaded content from s3://{bucket_name}/{s3_key}")
|
195 |
+
return content
|
196 |
+
except (NoCredentialsError, ClientError) as e:
|
197 |
+
print(f"Failed to download string data: {e}")
|
198 |
+
raise
|
199 |
+
|
200 |
+
def download_pdf_by_article_id(self, article_id, metadata, bucket_name, local_download_dir):
|
201 |
+
"""
|
202 |
+
Download a specific PDF from S3 by article ID.
|
203 |
+
|
204 |
+
Args:
|
205 |
+
article_id (str): The PMC article ID to download (e.g., "PMC1464409").
|
206 |
+
metadata (list): List of metadata records.
|
207 |
+
bucket_name (str): Name of the S3 bucket containing the files.
|
208 |
+
"""
|
209 |
+
# Search for the article in the metadata
|
210 |
+
record = next((item for item in metadata if item["pmc_id"] == article_id), None)
|
211 |
+
|
212 |
+
if not record:
|
213 |
+
print(f"Article ID {article_id} not found in metadata.")
|
214 |
+
return
|
215 |
+
|
216 |
+
pdf_s3_path = record.get("pdf_s3_path")
|
217 |
+
|
218 |
+
# Extract the S3 key from the S3 path
|
219 |
+
s3_key = pdf_s3_path.replace(f"s3://{bucket_name}/", "")
|
220 |
+
|
221 |
+
# Define the local file path
|
222 |
+
local_pdf_path = os.path.join(local_download_dir, f"{article_id}.pdf")
|
223 |
+
|
224 |
+
print(f"Downloading {article_id} from S3: {pdf_s3_path} to {local_pdf_path}")
|
225 |
+
|
226 |
+
# Download the file
|
227 |
+
try:
|
228 |
+
self.s3_client.download_file(bucket_name, s3_key, local_pdf_path)
|
229 |
+
print(f"Downloaded {article_id} to {local_pdf_path}")
|
230 |
+
except Exception as e:
|
231 |
+
print(f"Failed to download {article_id}: {e}")
|
232 |
+
|
233 |
+
if __name__ == "__main__":
|
234 |
+
|
235 |
+
s3 = S3Handler()
|
236 |
+
s3.list_files(bucket_name=settings.AWS_BUCKET_NAME)
|
237 |
+
# from botocore.config import Config
|
238 |
+
|
239 |
+
# Create custom configuration
|
240 |
+
# config = Config(
|
241 |
+
# region_name='me-south-1'
|
242 |
+
# )
|
243 |
+
|
244 |
+
# import boto3
|
245 |
+
# from botocore.exceptions import ClientError
|
246 |
+
|
247 |
+
# # Initialize the S3 client with exact credentials
|
248 |
+
# s3_client = boto3.client('s3', aws_access_key_id='AKIA4MTWHLYL52IGF2VY'.strip(), aws_secret_access_key='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'.strip(),
|
249 |
+
# config=config)
|
250 |
+
# # Test function
|
251 |
+
# def test_connection():
|
252 |
+
# try:
|
253 |
+
# # Try to list objects
|
254 |
+
# response = s3_client.list_objects_v2(
|
255 |
+
# Bucket='sehas3.bucket1'
|
256 |
+
# )
|
257 |
+
# try:
|
258 |
+
# bucket = s3_client.Bucket('sehas3.bucket1')
|
259 |
+
# for obj in bucket.objects.all():
|
260 |
+
# print(obj.key)
|
261 |
+
# print("Success!")
|
262 |
+
# except ClientError as e:
|
263 |
+
# print(f"Error: {e}")
|
264 |
+
# except ClientError as e:
|
265 |
+
# print(f"Error: {str(e)}")
|
266 |
+
|
267 |
+
# test_connection()
|
core/voice_processor.py
ADDED
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# audio_handler.py
|
2 |
+
import streamlit as st
|
3 |
+
import wave
|
4 |
+
import pyaudio
|
5 |
+
import tempfile
|
6 |
+
import os
|
7 |
+
from datetime import datetime
|
8 |
+
import openai
|
9 |
+
|
10 |
+
|
11 |
+
|
12 |
+
class AudioRecorder:
|
13 |
+
def __init__(self, openai_api_key):
|
14 |
+
self.audio = pyaudio.PyAudio()
|
15 |
+
self.frames = []
|
16 |
+
self.recording = False
|
17 |
+
self.stream = None
|
18 |
+
self.openai_client = openai.OpenAI(api_key=openai_api_key)
|
19 |
+
|
20 |
+
def start_recording(self):
|
21 |
+
self.recording = True
|
22 |
+
self.frames = []
|
23 |
+
self.stream = self.audio.open(
|
24 |
+
format=pyaudio.paInt16,
|
25 |
+
channels=1,
|
26 |
+
rate=44100,
|
27 |
+
input=True,
|
28 |
+
frames_per_buffer=1024,
|
29 |
+
stream_callback=self._callback
|
30 |
+
)
|
31 |
+
self.stream.start_stream()
|
32 |
+
|
33 |
+
def _callback(self, in_data, frame_count, time_info, status):
|
34 |
+
if self.recording:
|
35 |
+
self.frames.append(in_data)
|
36 |
+
return (in_data, pyaudio.paContinue)
|
37 |
+
|
38 |
+
def stop_recording(self):
|
39 |
+
if self.stream:
|
40 |
+
self.recording = False
|
41 |
+
self.stream.stop_stream()
|
42 |
+
self.stream.close()
|
43 |
+
|
44 |
+
# Create temporary file
|
45 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
46 |
+
audio_file = os.path.join(tempfile.gettempdir(), f"audio_{timestamp}.wav")
|
47 |
+
|
48 |
+
# Save audio file
|
49 |
+
with wave.open(audio_file, 'wb') as wf:
|
50 |
+
wf.setnchannels(1)
|
51 |
+
wf.setsampwidth(self.audio.get_sample_size(pyaudio.paInt16))
|
52 |
+
wf.setframerate(44100)
|
53 |
+
wf.writeframes(b''.join(self.frames))
|
54 |
+
|
55 |
+
return audio_file
|
56 |
+
return None
|
57 |
+
|
58 |
+
def transcribe_audio(self, audio_file_path):
|
59 |
+
"""Transcribe audio file using OpenAI Whisper API"""
|
60 |
+
try:
|
61 |
+
with open(audio_file_path, "rb") as audio_file:
|
62 |
+
transcript = self.openai_client.audio.transcriptions.create(
|
63 |
+
model="whisper-1",
|
64 |
+
file=audio_file,
|
65 |
+
language="en",
|
66 |
+
response_format="text"
|
67 |
+
)
|
68 |
+
return transcript
|
69 |
+
except Exception as e:
|
70 |
+
st.error(f"Transcription error: {str(e)}")
|
71 |
+
return None
|
72 |
+
def __del__(self):
|
73 |
+
self.audio.terminate()
|
database/__init__.py
ADDED
File without changes
|
database/vector_store.py
ADDED
File without changes
|
docker/Dockerfile
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Dockerfile
|
2 |
+
FROM python:3.11-slim
|
3 |
+
|
4 |
+
WORKDIR /app
|
5 |
+
|
6 |
+
COPY requirements.txt .
|
7 |
+
RUN sudo apt-get update && sudo apt-get install -y libportaudio2 libportaudiocpp0 portaudio19-dev python3-dev build-essential
|
8 |
+
|
9 |
+
|
10 |
+
|
11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
12 |
+
|
13 |
+
|
14 |
+
COPY . .
|
15 |
+
|
16 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
17 |
+
|
18 |
+
# docker-compose.yml
|
19 |
+
version: '3.8'
|
20 |
+
|
21 |
+
services:
|
22 |
+
api:
|
23 |
+
build:
|
24 |
+
context: .
|
25 |
+
dockerfile: docker/Dockerfile
|
26 |
+
ports:
|
27 |
+
- "8000:8000"
|
28 |
+
env_file:
|
29 |
+
- .env
|
30 |
+
volumes:
|
31 |
+
- .:/app
|
32 |
+
restart: unless-stopped
|
docker/docker-compose.yaml
ADDED
File without changes
|
main.py
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI
|
2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
3 |
+
from app.api import routes
|
4 |
+
from app.config import get_settings
|
5 |
+
|
6 |
+
settings = get_settings()
|
7 |
+
|
8 |
+
app = FastAPI(
|
9 |
+
title=settings.PROJECT_NAME
|
10 |
+
)
|
11 |
+
|
12 |
+
app.add_middleware(
|
13 |
+
CORSMiddleware,
|
14 |
+
allow_origins=["*"],
|
15 |
+
allow_credentials=True,
|
16 |
+
allow_methods=["*"],
|
17 |
+
allow_headers=["*"],
|
18 |
+
)
|
19 |
+
|
20 |
+
app.include_router(routes.router, prefix=settings.API_V1_STR)
|
qa_app.py
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app.py
|
2 |
+
from core.pineconeqa import PineconeQA
|
3 |
+
from core.voice_processor import AudioRecorder
|
4 |
+
import streamlit as st
|
5 |
+
from config import get_settings
|
6 |
+
import os
|
7 |
+
|
8 |
+
def initialize_qa():
|
9 |
+
"""Initialize QA system with API keys from settings"""
|
10 |
+
try:
|
11 |
+
settings = get_settings()
|
12 |
+
return PineconeQA(
|
13 |
+
pinecone_api_key=settings.PINECONE_API_KEY,
|
14 |
+
openai_api_key=settings.OPENAI_API_KEY,
|
15 |
+
index_name=settings.INDEX_NAME
|
16 |
+
)
|
17 |
+
except Exception as e:
|
18 |
+
st.error(f"Error initializing QA system: {str(e)}")
|
19 |
+
return None
|
20 |
+
|
21 |
+
def handle_audio_input():
|
22 |
+
"""Handle audio recording, saving, and transcription"""
|
23 |
+
settings = get_settings()
|
24 |
+
|
25 |
+
if 'audio_recorder' not in st.session_state:
|
26 |
+
st.session_state.audio_recorder = AudioRecorder(settings.OPENAI_API_KEY)
|
27 |
+
if 'transcribed_text' not in st.session_state:
|
28 |
+
st.session_state.transcribed_text = ""
|
29 |
+
|
30 |
+
col1, col2 = st.columns([1, 1])
|
31 |
+
|
32 |
+
with col1:
|
33 |
+
if st.button("🎤 Record",
|
34 |
+
help="Click to start recording",
|
35 |
+
type="primary" if not st.session_state.get('recording', False) else "secondary"):
|
36 |
+
st.session_state.audio_recorder.start_recording()
|
37 |
+
st.session_state.recording = True
|
38 |
+
st.info("Recording... Click 'Stop' when finished.")
|
39 |
+
|
40 |
+
with col2:
|
41 |
+
if st.button("⏹️ Stop",
|
42 |
+
help="Click to stop recording",
|
43 |
+
disabled=not st.session_state.get('recording', False)):
|
44 |
+
audio_file = st.session_state.audio_recorder.stop_recording()
|
45 |
+
st.session_state.recording = False
|
46 |
+
|
47 |
+
if audio_file:
|
48 |
+
with st.spinner("Transcribing audio..."):
|
49 |
+
# Transcribe audio
|
50 |
+
transcription = st.session_state.audio_recorder.transcribe_audio(audio_file)
|
51 |
+
if transcription:
|
52 |
+
# Store transcription in session state
|
53 |
+
st.session_state.transcribed_text = transcription
|
54 |
+
st.success("Audio transcribed successfully!")
|
55 |
+
# Play audio for verification
|
56 |
+
st.audio(audio_file)
|
57 |
+
else:
|
58 |
+
st.error("Failed to transcribe audio.")
|
59 |
+
|
60 |
+
def process_question(question):
|
61 |
+
st.session_state.chat_history = []
|
62 |
+
with st.spinner("Thinking..."):
|
63 |
+
response = st.session_state.qa_system.ask(question)
|
64 |
+
|
65 |
+
if "error" in response:
|
66 |
+
st.error(f"Error: {response['error']}")
|
67 |
+
else:
|
68 |
+
# Display answer
|
69 |
+
st.markdown("### Answer:")
|
70 |
+
st.write(response["answer"])
|
71 |
+
|
72 |
+
# Display sources
|
73 |
+
with st.expander("View Sources"):
|
74 |
+
for i, doc in enumerate(response["context"], 1):
|
75 |
+
st.markdown(f"**Source {i}:**")
|
76 |
+
st.write(f"Content: {doc.page_content}")
|
77 |
+
st.write(f"Source: {doc.metadata.get('source', 'Unknown')}")
|
78 |
+
st.write(f"Title: {doc.metadata.get('title', 'Unknown')}")
|
79 |
+
st.write(f"Keywords: {doc.metadata.get('keywords', 'N/A')}")
|
80 |
+
st.write(f"Publication Date: {doc.metadata.get('publication_date', 'Unknown')}")
|
81 |
+
st.markdown("---")
|
82 |
+
|
83 |
+
# Add to chat history
|
84 |
+
# st.session_state.chat_history.append((question, response["answer"]))
|
85 |
+
|
86 |
+
def main():
|
87 |
+
st.title("Scientific Paper Q&A System")
|
88 |
+
|
89 |
+
# Initialize session state variables
|
90 |
+
if 'qa_system' not in st.session_state:
|
91 |
+
st.session_state.qa_system = initialize_qa()
|
92 |
+
if 'chat_history' not in st.session_state:
|
93 |
+
st.session_state.chat_history = []
|
94 |
+
if 'recording' not in st.session_state:
|
95 |
+
st.session_state.recording = False
|
96 |
+
if 'transcribed_text' not in st.session_state:
|
97 |
+
st.session_state.transcribed_text = ""
|
98 |
+
|
99 |
+
# Main chat interface
|
100 |
+
if st.session_state.qa_system:
|
101 |
+
# Chat container
|
102 |
+
chat_container = st.container()
|
103 |
+
|
104 |
+
with chat_container:
|
105 |
+
# Display chat history
|
106 |
+
for i, (question, answer) in enumerate(st.session_state.chat_history):
|
107 |
+
st.markdown(f"**You:** {question}")
|
108 |
+
st.markdown(f"**Assistant:** {answer}")
|
109 |
+
st.markdown("---")
|
110 |
+
|
111 |
+
# Input container at the bottom
|
112 |
+
with st.container():
|
113 |
+
st.markdown("### Ask a Question")
|
114 |
+
|
115 |
+
# Text input area with transcribed text as default
|
116 |
+
question = st.text_area(
|
117 |
+
"Type your question or use voice input:",
|
118 |
+
value=st.session_state.transcribed_text,
|
119 |
+
height=100,
|
120 |
+
key="question_input",
|
121 |
+
label_visibility="collapsed"
|
122 |
+
)
|
123 |
+
|
124 |
+
# Audio recording interface
|
125 |
+
handle_audio_input()
|
126 |
+
|
127 |
+
# Ask button
|
128 |
+
if st.button("Send Question", type="primary"):
|
129 |
+
if question:
|
130 |
+
process_question(question)
|
131 |
+
# Clear transcribed text after sending
|
132 |
+
st.session_state.transcribed_text = ""
|
133 |
+
else:
|
134 |
+
st.warning("Please enter a question or record your voice.")
|
135 |
+
|
136 |
+
# Clear chat button
|
137 |
+
if st.button("Clear Chat"):
|
138 |
+
st.session_state.chat_history = []
|
139 |
+
st.session_state.transcribed_text = ""
|
140 |
+
st.rerun()
|
141 |
+
|
142 |
+
else:
|
143 |
+
st.error("Could not initialize QA system. Please check your environment variables.")
|
144 |
+
|
145 |
+
|
146 |
+
if __name__ == "__main__":
|
147 |
+
main()
|
rag_existing_index.ipynb
ADDED
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 4,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [
|
8 |
+
{
|
9 |
+
"name": "stderr",
|
10 |
+
"output_type": "stream",
|
11 |
+
"text": [
|
12 |
+
"/Users/larawehbe/Documents/fakkerai/sehatech/pinecone-env/lib/python3.11/site-packages/pinecone/data/index.py:1: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
13 |
+
" from tqdm.autonotebook import tqdm\n",
|
14 |
+
"/Users/larawehbe/Documents/fakkerai/sehatech/pinecone-env/lib/python3.11/site-packages/langchain_openai/embeddings/base.py:281: UserWarning: WARNING! return_source_documents is not default parameter.\n",
|
15 |
+
" return_source_documents was transferred to model_kwargs.\n",
|
16 |
+
" Please confirm that return_source_documents is what you intended.\n",
|
17 |
+
" warnings.warn(\n"
|
18 |
+
]
|
19 |
+
}
|
20 |
+
],
|
21 |
+
"source": [
|
22 |
+
"from langchain.vectorstores import Pinecone\n",
|
23 |
+
"from langchain_openai import OpenAIEmbeddings\n",
|
24 |
+
"import pinecone\n",
|
25 |
+
"from langchain_pinecone import PineconeVectorStore\n",
|
26 |
+
"from config import get_settings\n",
|
27 |
+
"settings = get_settings()\n",
|
28 |
+
"# Initialize Pinecone\n",
|
29 |
+
"pc = pinecone.Pinecone(settings.PINECONE_API_KEY)\n",
|
30 |
+
"import os \n",
|
31 |
+
"\n",
|
32 |
+
"os.environ['PINECONE_API_KEY'] = settings.PINECONE_API_KEY\n",
|
33 |
+
"\n",
|
34 |
+
"# Connect to the existing index\n",
|
35 |
+
"index_name = settings.INDEX_NAME # Replace with your index name\n",
|
36 |
+
"index = pc.Index(index_name)\n",
|
37 |
+
"\n",
|
38 |
+
"# Initialize embeddings (ensure your embedding logic matches the one used during indexing)\n",
|
39 |
+
"embeddings = OpenAIEmbeddings(openai_api_key=settings.OPENAI_API_KEY, return_source_documents=True)\n",
|
40 |
+
"\n",
|
41 |
+
"# Create the retriever from Pinecone\n",
|
42 |
+
"retriever = PineconeVectorStore(index=index, embedding=OpenAIEmbeddings())\n"
|
43 |
+
]
|
44 |
+
},
|
45 |
+
{
|
46 |
+
"cell_type": "code",
|
47 |
+
"execution_count": 5,
|
48 |
+
"metadata": {},
|
49 |
+
"outputs": [],
|
50 |
+
"source": [
|
51 |
+
"import os \n",
|
52 |
+
"# Define the LLM\n",
|
53 |
+
"os.environ[\"OPENAI_API_KEY\"] = settings.OPENAI_API_KEY\n",
|
54 |
+
"\n",
|
55 |
+
"from langchain_openai import ChatOpenAI\n",
|
56 |
+
"\n",
|
57 |
+
"llm = ChatOpenAI(model=\"gpt-4o\")"
|
58 |
+
]
|
59 |
+
},
|
60 |
+
{
|
61 |
+
"cell_type": "code",
|
62 |
+
"execution_count": 1,
|
63 |
+
"metadata": {},
|
64 |
+
"outputs": [
|
65 |
+
{
|
66 |
+
"ename": "NameError",
|
67 |
+
"evalue": "name 'llm' is not defined",
|
68 |
+
"output_type": "error",
|
69 |
+
"traceback": [
|
70 |
+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
71 |
+
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
|
72 |
+
"Cell \u001b[0;32mIn[1], line 25\u001b[0m\n\u001b[1;32m 17\u001b[0m prompt \u001b[38;5;241m=\u001b[39m ChatPromptTemplate\u001b[38;5;241m.\u001b[39mfrom_messages(\n\u001b[1;32m 18\u001b[0m [\n\u001b[1;32m 19\u001b[0m (\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124msystem\u001b[39m\u001b[38;5;124m\"\u001b[39m, system_prompt),\n\u001b[1;32m 20\u001b[0m (\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mhuman\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{input}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m),\n\u001b[1;32m 21\u001b[0m ]\n\u001b[1;32m 22\u001b[0m )\n\u001b[1;32m 24\u001b[0m \u001b[38;5;66;03m# Use your LLM instance\u001b[39;00m\n\u001b[0;32m---> 25\u001b[0m question_answer_chain \u001b[38;5;241m=\u001b[39m create_stuff_documents_chain(\u001b[43mllm\u001b[49m, prompt)\n\u001b[1;32m 27\u001b[0m \u001b[38;5;66;03m# Create the RAG chain\u001b[39;00m\n\u001b[1;32m 28\u001b[0m rag_chain \u001b[38;5;241m=\u001b[39m create_retrieval_chain(retriever\u001b[38;5;241m.\u001b[39mas_retriever(), question_answer_chain)\n",
|
73 |
+
"\u001b[0;31mNameError\u001b[0m: name 'llm' is not defined"
|
74 |
+
]
|
75 |
+
}
|
76 |
+
],
|
77 |
+
"source": [
|
78 |
+
"from langchain.chains import create_retrieval_chain\n",
|
79 |
+
"from langchain.chains.combine_documents import create_stuff_documents_chain\n",
|
80 |
+
"from langchain_core.prompts import ChatPromptTemplate\n",
|
81 |
+
"\n",
|
82 |
+
"# Define the system prompt\n",
|
83 |
+
"system_prompt = (\n",
|
84 |
+
" \"You are an assistant for question-answering tasks. \"\n",
|
85 |
+
" \"Use the following pieces of retrieved context to answer \"\n",
|
86 |
+
" \"the question. If you don't know the answer, say that you \"\n",
|
87 |
+
" \"don't know. Use three sentences maximum and keep the \"\n",
|
88 |
+
" \"answer concise.\"\n",
|
89 |
+
" \"\\n\\n\"\n",
|
90 |
+
" \"{context}\"\n",
|
91 |
+
")\n",
|
92 |
+
"\n",
|
93 |
+
"# Create the chat prompt template\n",
|
94 |
+
"prompt = ChatPromptTemplate.from_messages(\n",
|
95 |
+
" [\n",
|
96 |
+
" (\"system\", system_prompt),\n",
|
97 |
+
" (\"human\", \"{input}\"),\n",
|
98 |
+
" ]\n",
|
99 |
+
")\n",
|
100 |
+
"\n",
|
101 |
+
"# Use your LLM instance\n",
|
102 |
+
"question_answer_chain = create_stuff_documents_chain(llm, prompt)\n",
|
103 |
+
"\n",
|
104 |
+
"# Create the RAG chain\n",
|
105 |
+
"rag_chain = create_retrieval_chain(retriever.as_retriever(), question_answer_chain)\n",
|
106 |
+
"\n",
|
107 |
+
"# Invoke the RAG chain with a sample question\n",
|
108 |
+
"results = rag_chain.invoke({\"input\": \"pubmed_pdfs/PMC1474056.pdf\"})\n",
|
109 |
+
"print(results)\n"
|
110 |
+
]
|
111 |
+
},
|
112 |
+
{
|
113 |
+
"cell_type": "code",
|
114 |
+
"execution_count": null,
|
115 |
+
"metadata": {},
|
116 |
+
"outputs": [
|
117 |
+
{
|
118 |
+
"name": "stdout",
|
119 |
+
"output_type": "stream",
|
120 |
+
"text": [
|
121 |
+
"{'input': 'What are the benefits of using electronic health records for patient care??', 'context': [Document(id='chunk_100_63', metadata={'page': 0.0, 'source': 'pubmed_pdfs/PMC1474056.pdf'}, page_content='The use of sub-cellular localization data and functional\\nannotation as filters for the predictions increased their overlap\\nwith experimental complexes, as compared with the unfil-\\ntered predictions. This finding is in agreement with previousobservations that combining multiple sources of information\\nimproves the accuracy of function annotation as well as inter-\\naction prediction (9–11). Our method easily allows for theuse of additional biological filters when other types of\\ndata are available, such as synthetic gene lethality (36),\\nco-expression (37), and so on. This incremental addition oforthogonal information is also necessary to more accuratelyrepresent the conditions in the cellular milieu, where the\\npropensity of two protein structures to interact is not limited\\nonly by the physical chemistry of the interaction, but also byhigher levels of biological regulation, including compartmen-\\ntalization, expression, degradation, abundance and so on.'), Document(id='chunk_100_80', metadata={'page': 0.0, 'source': 'pubmed_pdfs/PMC1474056.pdf'}, page_content='23. Ghaemmaghami,S., Huh,W.K., Bower,K., Howson,R.W., Belle,A.,\\nDephoure,N., O’Shea,E.K. and Weissman,J.S. (2003) Global analysis\\nof protein expression in yeast. Nature ,425, 737–741.\\n24. Dwight,S.S., Harris,M.A., Dolinski,K., Ball,C.A., Binkley,G.,\\nChristie,K.R., Fisk,D.G., Issel-Tarver,L., Schroeder,M., Sherlock,G.et al. (2002) Saccharomyces Genome Database (SGD) provides\\nsecondary gene annotation using the gene ontology (GO). Nucleic\\nAcids Res. ,30, 69–72.\\n25. Fawcett,T. (2003) ROC graphs: notes and practical considerations for\\ndata mining researchers. Technical Report HPL-2003-4, HP Labs,Palo Alto, CA, USA.\\n26. Pieper,U., Eswar,N., Davis,F.P., Braberg,H., Madhusudhan,M.S.,\\nRossi,A., Marti-Renom,M., Karchin,R., Webb,B.M., Eranian,D. et al.\\n(2006) MODBASE: a database of annotated comparative proteinstructure models and associated resources. Nucleic Acids Res. ,34,\\nD291–D295.\\n27. Eswar,N., John,B., Mirkovic,N., Fiser,A., Ilyin,V.A., Pieper,U.,'), Document(id='chunk_100_73', metadata={'page': 0.0, 'source': 'pubmed_pdfs/PMC1474056.pdf'}, page_content='used to help bridge the resolution gap between electroncryo-microscopy (cryo-EM) density maps and atomic protein\\nstructures (41). Fitting of protein and protein domain models\\ninto density maps of large assemblies is already common, butdepending on the resolution, the information encoded in the\\nmap is often insufficient for an unambiguous determination\\nof the positions and orientations of the individual proteins(42). Models of the complexes predicted here may provideadditional restraints for a more accurate fitting of proteins\\ninto large complexes studied by cryo-EM and electron\\ncryo-tomography (14,43).\\nAs the number and size of experimentally determined\\nstructures of protein complexes increase, the number of\\ncomplexes that can be predicted and modeled using thesestructures as templates increases correspondingly, expanding\\nthe structural coverage of protein interaction space (44).'), Document(id='chunk_100_84', metadata={'page': 0.0, 'source': 'pubmed_pdfs/PMC1474056.pdf'}, page_content='Chung,S., Vidal,M. and Gerstein,M. (2004) Annotation transfer\\nbetween genomes: protein–protein interologs and protein–DNA\\nregulogs. Genome Res. ,14, 1107–1118.\\n39. Bornberg-Bauer,E., Beaussart,F., Kummerfeld,S.K., Teichmann,S.A.\\nand Weiner,J.,III (2005) The evolution of domain arrangementsin proteins and interaction networks. Cell Mol. Life Sci. ,62,\\n435–445.\\n40. Han,J.D., Bertin,N., Hao,T., Goldberg,D.S., Berriz,G.F., Zhang,L.V.,\\nDupuy,D., Walhout,A.J., Cusick,M.E., Roth,F.P. et al. (2004) Evidence\\nfor dynamically organized modularity in the yeast protein-proteininteraction network. Nature ,430, 88–93.\\n41. Topf,M. and Sali,A. (2005) Combining electron microscopy and\\ncomparative protein structure modeling. Curr. Opin. Struct. Biol. ,15,\\n578–585.\\n42. Fabiola,F. and Chapman,M.S. (2005) Fitting of high-resolution\\nstructures into electron microscopy reconstruction images. Structure ,\\n13, 389–400.\\n43. Sali,A., Glaeser,R., Earnest,T. and Baumeister,W. (2003) From words')], 'answer': 'The benefits of using electronic health records (EHRs) for patient care include improved accuracy and accessibility of patient information, which enhances coordination and reduces errors in treatment. EHRs facilitate better communication among healthcare providers, leading to more informed decision-making and streamlined care processes. Additionally, EHRs support data analysis for improved healthcare outcomes and can enhance patient engagement by providing them access to their health information.'}\n"
|
122 |
+
]
|
123 |
+
}
|
124 |
+
],
|
125 |
+
"source": [
|
126 |
+
"results = rag_chain.invoke({\"input\": \"What are the benefits of using electronic health records for patient care??\"})\n",
|
127 |
+
"\n",
|
128 |
+
"# Extract the answer and the source documents\n",
|
129 |
+
"print(results)"
|
130 |
+
]
|
131 |
+
},
|
132 |
+
{
|
133 |
+
"cell_type": "code",
|
134 |
+
"execution_count": null,
|
135 |
+
"metadata": {},
|
136 |
+
"outputs": [
|
137 |
+
{
|
138 |
+
"data": {
|
139 |
+
"text/plain": [
|
140 |
+
"{'input': 'How does the algorithm perform when multiple templates for binding modes are available?',\n",
|
141 |
+
" 'context': [Document(id='chunk_100_67', metadata={'page': 0.0, 'source': 'pubmed_pdfs/PMC1474056.pdf'}, page_content='The ability of the algorithm to choose the correct binding\\nmode when multiple templates are available was illustrated\\nby evaluation of three alternative binding modes that have\\nbeen structurally characterized between porcine pancreatica-amylase and camelid VHH domains (Figure 5). The\\nalgorithm successfully chose the native binding mode for\\nall three VHH domains. In addition, the statistical potentialscores that were computed for the native binding modesexhibit the same rank order as the affinity of the interactions\\nmeasured by total internal reflectance (33).\\nHowever, this example is also cautionary in that each VHH\\ndomain had one non-native mode that scored below the\\noptimal Z-score threshold, though only the native modes\\nproduced negative raw scores (Results). In a large-scalepredictive setting, if the native binding mode was not\\navailable as a template, the VHH domain would have been'),\n",
|
142 |
+
" Document(id='chunk_100_39', metadata={'page': 0.0, 'source': 'pubmed_pdfs/PMC1474056.pdf'}, page_content='how many of the predicted complexes were equivalent to,or were subcomplexes of, experimentally determined com-\\nplexes. Since the predictions are based on known structures,\\nthe sizes of the predicted complexes are far smaller than thoseobtained by biochemical methods such as tandem affinitypurification methods. For this reason, we elected not to use\\na metric that explicitly penalizes size differences [e.g. the\\nmetric defined in Ref. (16)].\\nBinding mode selection\\nThe ability of the potential to select the proper binding mode\\nwhen multiple template interfaces of different orientation are\\navailable was assessed. The test cases used were the struc-\\ntures of camelid VHH domains AMB7, AMD10 and AMD9bound to porcine pancreatic a-amylase (PPA) (PDB codes\\n1kxt, 1kxv and 1kxq, respectively). All three modes were\\nevaluated for each VHH–PPA complex using the interfacestatistical potential.\\nData sources\\nThe prediction algorithm uses three types of data: (i) target'),\n",
|
143 |
+
" Document(id='chunk_100_68', metadata={'page': 0.0, 'source': 'pubmed_pdfs/PMC1474056.pdf'}, page_content='produced negative raw scores (Results). In a large-scalepredictive setting, if the native binding mode was not\\navailable as a template, the VHH domain would have been\\npredicted to interact with PPA, but through an incorrectbinding mode. This example illustrates a connection between\\nthe observed performance and the underlying scoring scheme.\\nHowever, a systematic analysis of alternative binding modesin protein interactions, and the ability of our method to dis-tinguish them, remains a useful goal for the future.2950 Nucleic Acids Research, 2006, Vol. 34, No. 10Network specificities\\nA more difficult test of the method is the prediction of\\nspecificities within interaction networks between homologous\\nproteins. To address this problem, the method was applied to\\npredict the specificities within the epidermal growth factorreceptor (EGFR) and tumor necrosis factor b(TNFb) net-\\nworks of ligand receptor interactions (data not shown). In\\nboth networks the method failed to recapitulate known bind-'),\n",
|
144 |
+
" Document(id='chunk_100_56', metadata={'page': 0.0, 'source': 'pubmed_pdfs/PMC1474056.pdf'}, page_content='structural assessment, PSI-BLAST (32) was used to\\npredict binary interactions by detecting similarities between\\nS.cerevisiae proteins and the template complexes. An overlap\\nof 929 binary interactions was observed between the set of\\n36 790 (2.5%) predictions and the 19 424 (4.8%) experimen-\\ntally observed binary interactions.\\nAlternate binding modes\\nThe ability of the algorithm to correctly select the native\\nbinding mode when alternate templates are available wastested. The native binding mode was correctly selected for\\nall three VHH domains interacting with porcine pancreatic\\na-amylase (Figure 5). In addition, the statistical potential\\nscores that were computed for the native binding modesexhibit the same rank-order as the affinity measured experi-\\nmentally by total internal reflectance (33).\\nCo-complexed domains\\nAn extension process merged predicted complexes containing\\ndifferent domains of a single target protein (Figure 1c). This')],\n",
|
145 |
+
" 'answer': 'The algorithm successfully chooses the native binding mode for all three VHH domains interacting with porcine pancreatic a-amylase when multiple templates are available. The statistical potential scores for the native binding modes exhibit the same rank order as the experimentally measured affinity. However, each VHH domain had one non-native mode that scored below the optimal Z-score threshold, indicating some caution is necessary in large-scale predictive settings.'}"
|
146 |
+
]
|
147 |
+
},
|
148 |
+
"execution_count": 9,
|
149 |
+
"metadata": {},
|
150 |
+
"output_type": "execute_result"
|
151 |
+
}
|
152 |
+
],
|
153 |
+
"source": [
|
154 |
+
"results = rag_chain.invoke({\"input\": \"How does the algorithm perform when multiple templates for binding modes are available?\"})\n",
|
155 |
+
"\n",
|
156 |
+
"\n",
|
157 |
+
"# Ensure the output includes the answer and source documents\n",
|
158 |
+
"\n",
|
159 |
+
"results"
|
160 |
+
]
|
161 |
+
},
|
162 |
+
{
|
163 |
+
"cell_type": "code",
|
164 |
+
"execution_count": null,
|
165 |
+
"metadata": {},
|
166 |
+
"outputs": [],
|
167 |
+
"source": []
|
168 |
+
}
|
169 |
+
],
|
170 |
+
"metadata": {
|
171 |
+
"kernelspec": {
|
172 |
+
"display_name": "pinecone-env",
|
173 |
+
"language": "python",
|
174 |
+
"name": "python3"
|
175 |
+
},
|
176 |
+
"language_info": {
|
177 |
+
"codemirror_mode": {
|
178 |
+
"name": "ipython",
|
179 |
+
"version": 3
|
180 |
+
},
|
181 |
+
"file_extension": ".py",
|
182 |
+
"mimetype": "text/x-python",
|
183 |
+
"name": "python",
|
184 |
+
"nbconvert_exporter": "python",
|
185 |
+
"pygments_lexer": "ipython3",
|
186 |
+
"version": "3.11.10"
|
187 |
+
}
|
188 |
+
},
|
189 |
+
"nbformat": 4,
|
190 |
+
"nbformat_minor": 2
|
191 |
+
}
|
requirements.txt
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
fitz
|
2 |
+
pinecone
|
3 |
+
openai
|
4 |
+
fastapi
|
5 |
+
pypdf
|
6 |
+
pypdf2
|
7 |
+
langchain_community
|
8 |
+
langchain-openai
|
9 |
+
langchain-pinecone
|
10 |
+
boto3
|
11 |
+
pyaudio
|
12 |
+
gradio
|
sandbox_testing.ipynb
ADDED
@@ -0,0 +1,1073 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 60,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [],
|
8 |
+
"source": [
|
9 |
+
"from core.s3_utils import S3Handler"
|
10 |
+
]
|
11 |
+
},
|
12 |
+
{
|
13 |
+
"cell_type": "code",
|
14 |
+
"execution_count": 61,
|
15 |
+
"metadata": {},
|
16 |
+
"outputs": [],
|
17 |
+
"source": [
|
18 |
+
"s3_handler = S3Handler()\n",
|
19 |
+
"from config import get_settings\n",
|
20 |
+
"settings = get_settings()"
|
21 |
+
]
|
22 |
+
},
|
23 |
+
{
|
24 |
+
"cell_type": "code",
|
25 |
+
"execution_count": 62,
|
26 |
+
"metadata": {},
|
27 |
+
"outputs": [
|
28 |
+
{
|
29 |
+
"name": "stdout",
|
30 |
+
"output_type": "stream",
|
31 |
+
"text": [
|
32 |
+
"\n",
|
33 |
+
"Files in bucket 'sehas3.bucket1' with prefix '':\n",
|
34 |
+
"No files found\n"
|
35 |
+
]
|
36 |
+
},
|
37 |
+
{
|
38 |
+
"data": {
|
39 |
+
"text/plain": [
|
40 |
+
"[]"
|
41 |
+
]
|
42 |
+
},
|
43 |
+
"execution_count": 62,
|
44 |
+
"metadata": {},
|
45 |
+
"output_type": "execute_result"
|
46 |
+
}
|
47 |
+
],
|
48 |
+
"source": [
|
49 |
+
"s3_handler.list_files(settings.AWS_BUCKET_NAME)"
|
50 |
+
]
|
51 |
+
},
|
52 |
+
{
|
53 |
+
"cell_type": "code",
|
54 |
+
"execution_count": 63,
|
55 |
+
"metadata": {},
|
56 |
+
"outputs": [
|
57 |
+
{
|
58 |
+
"name": "stdout",
|
59 |
+
"output_type": "stream",
|
60 |
+
"text": [
|
61 |
+
"Failed to download string data: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.\n"
|
62 |
+
]
|
63 |
+
},
|
64 |
+
{
|
65 |
+
"ename": "NoSuchKey",
|
66 |
+
"evalue": "An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist.",
|
67 |
+
"output_type": "error",
|
68 |
+
"traceback": [
|
69 |
+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
70 |
+
"\u001b[0;31mNoSuchKey\u001b[0m Traceback (most recent call last)",
|
71 |
+
"Cell \u001b[0;32mIn[63], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mjson\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m metadata_content \u001b[38;5;241m=\u001b[39m \u001b[43ms3_handler\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdownload_string_from_s3\u001b[49m\u001b[43m(\u001b[49m\u001b[43msettings\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mAWS_BUCKET_NAME\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mpubmed_metadata/metadata.json\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 3\u001b[0m metadata_content \u001b[38;5;241m=\u001b[39m json\u001b[38;5;241m.\u001b[39mloads(metadata_content)\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28mlen\u001b[39m(metadata_content)\n",
|
72 |
+
"File \u001b[0;32m~/Documents/fakkerai/sehatech/core/s3_utils.py:192\u001b[0m, in \u001b[0;36mS3Handler.download_string_from_s3\u001b[0;34m(self, bucket_name, s3_key)\u001b[0m\n\u001b[1;32m 181\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 182\u001b[0m \u001b[38;5;124;03mDownload a string object from S3 and return it.\u001b[39;00m\n\u001b[1;32m 183\u001b[0m \n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 189\u001b[0m \u001b[38;5;124;03m str: The content of the object as a string.\u001b[39;00m\n\u001b[1;32m 190\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 191\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[0;32m--> 192\u001b[0m response \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43ms3_client\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_object\u001b[49m\u001b[43m(\u001b[49m\u001b[43mBucket\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbucket_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mKey\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43ms3_key\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 193\u001b[0m content \u001b[38;5;241m=\u001b[39m response[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mBody\u001b[39m\u001b[38;5;124m'\u001b[39m]\u001b[38;5;241m.\u001b[39mread()\u001b[38;5;241m.\u001b[39mdecode(\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mutf-8\u001b[39m\u001b[38;5;124m'\u001b[39m)\n\u001b[1;32m 194\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDownloaded content from s3://\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mbucket_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m/\u001b[39m\u001b[38;5;132;01m{\u001b[39;00ms3_key\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n",
|
73 |
+
"File \u001b[0;32m~/Documents/fakkerai/sehatech/pinecone-env/lib/python3.11/site-packages/botocore/client.py:569\u001b[0m, in \u001b[0;36mClientCreator._create_api_method.<locals>._api_call\u001b[0;34m(self, *args, **kwargs)\u001b[0m\n\u001b[1;32m 565\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\n\u001b[1;32m 566\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mpy_operation_name\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m() only accepts keyword arguments.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 567\u001b[0m )\n\u001b[1;32m 568\u001b[0m \u001b[38;5;66;03m# The \"self\" in this scope is referring to the BaseClient.\u001b[39;00m\n\u001b[0;32m--> 569\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_make_api_call\u001b[49m\u001b[43m(\u001b[49m\u001b[43moperation_name\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
|
74 |
+
"File \u001b[0;32m~/Documents/fakkerai/sehatech/pinecone-env/lib/python3.11/site-packages/botocore/client.py:1023\u001b[0m, in \u001b[0;36mBaseClient._make_api_call\u001b[0;34m(self, operation_name, api_params)\u001b[0m\n\u001b[1;32m 1019\u001b[0m error_code \u001b[38;5;241m=\u001b[39m error_info\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mQueryErrorCode\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;129;01mor\u001b[39;00m error_info\u001b[38;5;241m.\u001b[39mget(\n\u001b[1;32m 1020\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mCode\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 1021\u001b[0m )\n\u001b[1;32m 1022\u001b[0m error_class \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mexceptions\u001b[38;5;241m.\u001b[39mfrom_code(error_code)\n\u001b[0;32m-> 1023\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m error_class(parsed_response, operation_name)\n\u001b[1;32m 1024\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[1;32m 1025\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m parsed_response\n",
|
75 |
+
"\u001b[0;31mNoSuchKey\u001b[0m: An error occurred (NoSuchKey) when calling the GetObject operation: The specified key does not exist."
|
76 |
+
]
|
77 |
+
}
|
78 |
+
],
|
79 |
+
"source": [
|
80 |
+
"import json\n",
|
81 |
+
"metadata_content = s3_handler.download_string_from_s3(settings.AWS_BUCKET_NAME, \"pubmed_metadata/metadata.json\")\n",
|
82 |
+
"metadata_content = json.loads(metadata_content)\n",
|
83 |
+
"\n",
|
84 |
+
"len(metadata_content)\n"
|
85 |
+
]
|
86 |
+
},
|
87 |
+
{
|
88 |
+
"cell_type": "code",
|
89 |
+
"execution_count": 64,
|
90 |
+
"metadata": {},
|
91 |
+
"outputs": [
|
92 |
+
{
|
93 |
+
"data": {
|
94 |
+
"text/plain": [
|
95 |
+
"[{'pmc_id': 'PMC8419487',\n",
|
96 |
+
" 'title': 'No Title Available',\n",
|
97 |
+
" 'abstract': 'ZnS materials have been widely used in fluorescence biosensors to characterize different types of stem cells due to their excellent fluorescence effect. In this study, ZnS was prepared by vulcanizing nano-Zn particles synthesized using a DC arc plasma. The composition and structure of the ZnS materials were studied by X-ray diffraction (XRD), and their functional group information and optical properties were investigated by using IR spectrophotometry and UV-vis spectrophotometry. It has been found that the synthesized materials consist of Zn, cubic ZnS, and hexagonal ZnS according to the vulcanization parameters. Crystalline ZnS was gradually transformed from a cubic to a hexagonal structure, and the cycling properties first increase, then decrease with increasing sulfurization temperature. There is an optimal curing temperature giving the best cycling performance and specific capacity: the material sulfurized thereat mainly consists of cubic ',\n",
|
98 |
+
" 'authors': ['Ren, Y.',\n",
|
99 |
+
" 'Zhou, H.',\n",
|
100 |
+
" 'Wang, X.',\n",
|
101 |
+
" 'Liu, Q. W.',\n",
|
102 |
+
" 'Hou, X. D.',\n",
|
103 |
+
" 'Zhang, G. F.'],\n",
|
104 |
+
" 'keywords': [],\n",
|
105 |
+
" 'doi': 'N/A',\n",
|
106 |
+
" 'source': 'downloaded_pdfs/PMC8419487/PMC8419487/SCI2021-7067146.nxml',\n",
|
107 |
+
" 'publication_date': '2021'}]"
|
108 |
+
]
|
109 |
+
},
|
110 |
+
"execution_count": 64,
|
111 |
+
"metadata": {},
|
112 |
+
"output_type": "execute_result"
|
113 |
+
}
|
114 |
+
],
|
115 |
+
"source": [
|
116 |
+
"metadata_content"
|
117 |
+
]
|
118 |
+
},
|
119 |
+
{
|
120 |
+
"cell_type": "code",
|
121 |
+
"execution_count": 65,
|
122 |
+
"metadata": {},
|
123 |
+
"outputs": [
|
124 |
+
{
|
125 |
+
"name": "stdout",
|
126 |
+
"output_type": "stream",
|
127 |
+
"text": [
|
128 |
+
"No files found in bucket 'sehas3.bucket1' with prefix ''\n",
|
129 |
+
"\n",
|
130 |
+
"Successfully deleted 0 files\n"
|
131 |
+
]
|
132 |
+
},
|
133 |
+
{
|
134 |
+
"data": {
|
135 |
+
"text/plain": [
|
136 |
+
"(0, [])"
|
137 |
+
]
|
138 |
+
},
|
139 |
+
"execution_count": 65,
|
140 |
+
"metadata": {},
|
141 |
+
"output_type": "execute_result"
|
142 |
+
}
|
143 |
+
],
|
144 |
+
"source": [
|
145 |
+
"s3_handler.delete_all_files(settings.AWS_BUCKET_NAME)"
|
146 |
+
]
|
147 |
+
},
|
148 |
+
{
|
149 |
+
"cell_type": "code",
|
150 |
+
"execution_count": 4,
|
151 |
+
"metadata": {},
|
152 |
+
"outputs": [
|
153 |
+
{
|
154 |
+
"name": "stderr",
|
155 |
+
"output_type": "stream",
|
156 |
+
"text": [
|
157 |
+
"/Users/larawehbe/Documents/fakkerai/sehatech/venv/lib/python3.13/site-packages/pinecone/data/index.py:1: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
158 |
+
" from tqdm.autonotebook import tqdm\n"
|
159 |
+
]
|
160 |
+
}
|
161 |
+
],
|
162 |
+
"source": [
|
163 |
+
"import pinecone\n",
|
164 |
+
"pc = pinecone.Pinecone(settings.PINECONE_API_KEY)"
|
165 |
+
]
|
166 |
+
},
|
167 |
+
{
|
168 |
+
"cell_type": "code",
|
169 |
+
"execution_count": 13,
|
170 |
+
"metadata": {},
|
171 |
+
"outputs": [],
|
172 |
+
"source": [
|
173 |
+
"index = pc.Index(settings.INDEX_NAME)\n"
|
174 |
+
]
|
175 |
+
},
|
176 |
+
{
|
177 |
+
"cell_type": "code",
|
178 |
+
"execution_count": 15,
|
179 |
+
"metadata": {},
|
180 |
+
"outputs": [
|
181 |
+
{
|
182 |
+
"data": {
|
183 |
+
"text/plain": [
|
184 |
+
"{'matches': [], 'namespace': 'default', 'usage': {'read_units': 1}}"
|
185 |
+
]
|
186 |
+
},
|
187 |
+
"execution_count": 15,
|
188 |
+
"metadata": {},
|
189 |
+
"output_type": "execute_result"
|
190 |
+
}
|
191 |
+
],
|
192 |
+
"source": [
|
193 |
+
"index.query(namespace=\"default\", top_k=1)"
|
194 |
+
]
|
195 |
+
},
|
196 |
+
{
|
197 |
+
"cell_type": "markdown",
|
198 |
+
"metadata": {},
|
199 |
+
"source": [
|
200 |
+
"#### temp : remove when tested"
|
201 |
+
]
|
202 |
+
},
|
203 |
+
{
|
204 |
+
"cell_type": "code",
|
205 |
+
"execution_count": 2,
|
206 |
+
"metadata": {},
|
207 |
+
"outputs": [],
|
208 |
+
"source": [
|
209 |
+
"import PyPDF2"
|
210 |
+
]
|
211 |
+
},
|
212 |
+
{
|
213 |
+
"cell_type": "code",
|
214 |
+
"execution_count": 5,
|
215 |
+
"metadata": {},
|
216 |
+
"outputs": [],
|
217 |
+
"source": [
|
218 |
+
"from PyPDF2 import PdfReader"
|
219 |
+
]
|
220 |
+
},
|
221 |
+
{
|
222 |
+
"cell_type": "code",
|
223 |
+
"execution_count": null,
|
224 |
+
"metadata": {},
|
225 |
+
"outputs": [],
|
226 |
+
"source": [
|
227 |
+
"pdf = PdfReader(\"WJR-9-27.pdf\")\n",
|
228 |
+
"info = pdf.metadata\n",
|
229 |
+
"\n"
|
230 |
+
]
|
231 |
+
},
|
232 |
+
{
|
233 |
+
"cell_type": "code",
|
234 |
+
"execution_count": 26,
|
235 |
+
"metadata": {},
|
236 |
+
"outputs": [],
|
237 |
+
"source": [
|
238 |
+
"text = \"\".join(page.extract_text() for page in pdf.pages)"
|
239 |
+
]
|
240 |
+
},
|
241 |
+
{
|
242 |
+
"cell_type": "code",
|
243 |
+
"execution_count": 27,
|
244 |
+
"metadata": {},
|
245 |
+
"outputs": [
|
246 |
+
{
|
247 |
+
"name": "stdout",
|
248 |
+
"output_type": "stream",
|
249 |
+
"text": [
|
250 |
+
"Matteo Bauckneht, Roberta Piva, Gianmario Sambuceti, Francesco Grossi, Silvia Morbelli EDITORIAL\n",
|
251 |
+
"27 February 28, 2017 |Volume 9 |Issue 2| WJR|www.wjgnet.comEvaluation of response to immune checkpoint inhibitors: Is \n",
|
252 |
+
"there a role for positron emission tomography?\n",
|
253 |
+
"Matteo Bauckneht, Roberta Piva, Gianmario Sambuceti, \n",
|
254 |
+
"Silvia Morbelli, Nuclear Medicine Unit, IRCCS San Martino-\n",
|
255 |
+
"IST, University of Genoa, 16132 Genoa, Italy\n",
|
256 |
+
"Francesco Grossi, Lung Cancer Unit, IRCCS San Martino-IST, \n",
|
257 |
+
"University of Genoa, 16132 Genoa, Italy\n",
|
258 |
+
"Author contributions: Morbelli S conceived and designed \n",
|
259 |
+
"the study; Bauckneht M and Morbelli S drafted the manuscript; \n",
|
260 |
+
"Bauckneht M and Piva R prepared the tables and figures; \n",
|
261 |
+
"Sambuceti G and Grossi F critically revised the manuscript; all \n",
|
262 |
+
"the authors approved the final version of the paper.\n",
|
263 |
+
"Conflict-of-interest statement: The authors have no conflicts of \n",
|
264 |
+
"interest related to this publication to disclose.\n",
|
265 |
+
"Open-Access: This article is an open-access article which was \n",
|
266 |
+
"selected by an in-house editor and fully peer-reviewed by external \n",
|
267 |
+
"reviewers. It is distributed in accordance with the Creative Commons \n",
|
268 |
+
"Attribution Non Commercial (CC BY-NC 4.0) license, which \n",
|
269 |
+
"permits others to distribute, remix, adapt, build upon this work non-\n",
|
270 |
+
"commercially, and license their derivative works on different terms, \n",
|
271 |
+
"provided the original work is properly cited and the use is non-\n",
|
272 |
+
"commercial. See: http://creativecommons.org/licenses/by-nc/4.0/\n",
|
273 |
+
"Manuscript source: Invited manuscript\n",
|
274 |
+
"Correspondence to: Silvia Morbelli, MD, PhD, Nuclear \n",
|
275 |
+
"Medicine Unit, IRCCS San Martino-IST, University of Genoa, \n",
|
276 |
+
"Largo R. Benzi 10, 16132 Genova, \n",
|
277 |
+
"Italy. silviadaniela.morbelli@hsanmartino.i t \n",
|
278 |
+
"Telephone: +39-010-5552026\n",
|
279 |
+
"Fax: +39-010-5556911\n",
|
280 |
+
"Received: August 20, 2016 \n",
|
281 |
+
"Peer-review started: August 23, 2016 \n",
|
282 |
+
"First decision: October 21, 2016\n",
|
283 |
+
"Revised: November 2, 2016 \n",
|
284 |
+
"Accepted: November 27, 2016\n",
|
285 |
+
"Article in press: November 29, 2016\n",
|
286 |
+
"Published online: February 28, 2017\n",
|
287 |
+
"Abstract\n",
|
288 |
+
"Strategies targeting intracellular negative regulators such as immune checkpoint inhibitors (ICPIs) have \n",
|
289 |
+
"demonstrated significant antitumor activity across a \n",
|
290 |
+
"wide range of solid tumors. In the clinical practice, the \n",
|
291 |
+
"radiological effect of immunotherapeutic agents has \n",
|
292 |
+
"raised several more relevant and complex challenges for \n",
|
293 |
+
"the determination of their imaging-based response at \n",
|
294 |
+
"single patient level. Accordingly, it has been suggested \n",
|
295 |
+
"that the conventional Response Evaluation Criteria in \n",
|
296 |
+
"Solid Tumors assessment alone, based on dimensional \n",
|
297 |
+
"evaluation provided by computed tomography (CT), \n",
|
298 |
+
"tends to underestimate the benefit of ICPIs at least in \n",
|
299 |
+
"a subset of patients, supporting the need of immune-\n",
|
300 |
+
"related response criteria. Different from CT, very few \n",
|
301 |
+
"data are available for the evaluation of immunotherapy \n",
|
302 |
+
"by means of 18F-fluoro-2-deoxy-D-glucose positron \n",
|
303 |
+
"emission tomography (FDG-PET). Moreover, since \n",
|
304 |
+
"the antineoplastic activity of ICPIs is highly related \n",
|
305 |
+
"to the activation of T cells against cancer cells, FDG \n",
|
306 |
+
"accumulation might cause false-positive findings. Yet, \n",
|
307 |
+
"discrimination between benign and malignant processes \n",
|
308 |
+
"represents a huge challenge for FDG-PET in this clinical \n",
|
309 |
+
"setting. Consequently, it might be of high interest to test \n",
|
310 |
+
"the complex and variegated response to ICPIs by means \n",
|
311 |
+
"of PET and thus it is worthwhile to ask if a similar \n",
|
312 |
+
"introduction of immune-related PET-based criteria could \n",
|
313 |
+
"be proposed in the future. Finally, PET might offer a new \n",
|
314 |
+
"insight into the biology and pathophysiology of ICPIs \n",
|
315 |
+
"thanks to a growing number of non-invasive immune-\n",
|
316 |
+
"diagnostic approaches based on non-FDG tracers.\n",
|
317 |
+
"Key words: Immune checkpoint inhibitors; Positron \n",
|
318 |
+
"emission tomography; Computed tomography; 18F-fluoro-\n",
|
319 |
+
"2-deoxy-D-glucose; Non-18F-fluoro-2-deoxy-D-glucose \n",
|
320 |
+
"tracers\n",
|
321 |
+
"© The Author(s) 2017. Published by Baishideng Publishing \n",
|
322 |
+
"Group Inc. All rights reserved.\n",
|
323 |
+
"Core tip: In the clinical practice, the radiological \n",
|
324 |
+
"interpretation of immunotherapy effects represents \n",
|
325 |
+
"a huge challenge at single patient level. However, \n",
|
326 |
+
"although the computed tomography-based response World Journal of \n",
|
327 |
+
"Radiology WJR\n",
|
328 |
+
"Submit a Manuscript: http://www.wjgnet.com/esps/\n",
|
329 |
+
"DOI: 10.4329/wjr.v9.i2.27World J Radiol 2017 February 28; 9(2): 27-33\n",
|
330 |
+
"ISSN 1949-8470 (online)28 February 28, 2017 |Volume 9 |Issue 2| WJR|www.wjgnet.comBauckneht M et al. Immune checkpoint inhibitors and PET\n",
|
331 |
+
"evaluation for immune checkpoint inhibitors (ICPIs) is \n",
|
332 |
+
"feasible thanks to the introduction of immune-related \n",
|
333 |
+
"response criteria, very few data are available for the \n",
|
334 |
+
"potential role of 18F-fluoro-2-deoxy-D-glucose positron \n",
|
335 |
+
"emission tomography (FDG-PET). Due to the intrinsic \n",
|
336 |
+
"nature of FDG accumulation pathophysiology, it might \n",
|
337 |
+
"be central to test the complex and variegated response \n",
|
338 |
+
"to ICPIs by means of PET. Finally, PET might offer a new \n",
|
339 |
+
"insight into the biology of ICPIs thanks to a growing \n",
|
340 |
+
"number of non-invasive immune-diagnostic approaches \n",
|
341 |
+
"based on non-FDG tracers.\n",
|
342 |
+
"Bauckneht M, Piva R, Sambuceti G, Grossi F, Morbelli S. \n",
|
343 |
+
"Evaluation of response to immune checkpoint inhibitors: Is \n",
|
344 |
+
"there a role for positron emission tomography? World J Radiol \n",
|
345 |
+
"2017; 9(2): 27-33 Available from: URL: http://www.wjgnet.\n",
|
346 |
+
"com/ 1949-8470 /full/v9/i2/27.htm DOI: http://dx.doi.org/10.4329/\n",
|
347 |
+
"wjr.v9.i2.27\n",
|
348 |
+
"TEXT\n",
|
349 |
+
"The function of the immune system is characterized by \n",
|
350 |
+
"multiple checkpoints aiming to avoid its over-activation \n",
|
351 |
+
"against healthy cells (self-tolerance)[1]. Cancer cells may \n",
|
352 |
+
"take advantage of these checkpoints to escape detection \n",
|
353 |
+
"by the immune system. Some of these checkpoints such \n",
|
354 |
+
"as cytotoxic T-lymphocyte-associated antigen 4 (CTLA-4) \n",
|
355 |
+
"and programmed cell death protein 1 (PD-1) have been \n",
|
356 |
+
"extensively studied as t argets in the frame of the so-\n",
|
357 |
+
"called cancer immunotherapy[1]. CTLA-4 counteracts \n",
|
358 |
+
"the activity of the T cell co-stimulatory receptor CD28 \n",
|
359 |
+
"and actively delivers inhibitory signals to the T cell[2]. \n",
|
360 |
+
"PD-1 has two ligands, PD1 ligand 1 (PDL1) and PDL2, \n",
|
361 |
+
"and its inhibitory effect is accomplished through a dual \n",
|
362 |
+
"mechanism of promoting apoptosis in antigen specific \n",
|
363 |
+
"T-cells in lymph nodes while simultaneously reducing \n",
|
364 |
+
"apoptosis in regulatory T cells (suppressor T cells)[3]. In \n",
|
365 |
+
"the last few years, the blockade of immune checkpoints \n",
|
366 |
+
"has disclosed the potential of the antitumor immune \n",
|
367 |
+
"response in a fashion that is transforming human cancer \n",
|
368 |
+
"therapeutics. CTLA4 antibodies such as ipilimumab \n",
|
369 |
+
"and tremelimumab have been tested in the last ten \n",
|
370 |
+
"years in different types of cancer , starting with patients \n",
|
371 |
+
"with advanced melanoma[4]. Ipilimumab was the first \n",
|
372 |
+
"therapy to demonstrate a survival benefit for patients \n",
|
373 |
+
"with metastatic melanoma. In a study by Hodi et al[5], \n",
|
374 |
+
"ipilimumab significantly improved overall survival in \n",
|
375 |
+
"patients with previously treated metastatic melanoma \n",
|
376 |
+
"and the drug was approved by the United States Food \n",
|
377 |
+
"and Drug Administration (FDA) for the treatment of \n",
|
378 |
+
"advanced melanoma in 2011[5]. Similarly, nivolumab, \n",
|
379 |
+
"a humanized anti-PD-1 monoclonal antibody, has \n",
|
380 |
+
"demonstrated durable responses in several phase III \n",
|
381 |
+
"trials and has received FDA approval in specific clinical \n",
|
382 |
+
"settings in patients with melanoma, renal cell cancer , \n",
|
383 |
+
"Hodgkin���s lymphoma, bladder cancer, and non-small \n",
|
384 |
+
"cell lung cancer (NSCLC)[6-9]. Figure 1 summarizes the mechanisms of action of the two FDA approved immune \n",
|
385 |
+
"checkpoint inhibitors (ICPIs) .\n",
|
386 |
+
"Evaluation of response to ICPIs \n",
|
387 |
+
"Historically, the Response Evaluation Criteria in Solid \n",
|
388 |
+
"Tumors (RECIST) has been validated and used to eva -\n",
|
389 |
+
"luate antitumor responses to chemotherapeutic agents[10] \n",
|
390 |
+
"(Table 1 for a more detailed description). These criteria \n",
|
391 |
+
"are based on dimensional evaluation and rely on the \n",
|
392 |
+
"fact that the cytotoxic effect of chemotherapeutic agents \n",
|
393 |
+
"tends to translate into measurable effects in terms of \n",
|
394 |
+
"tumor shrinkage from baseline. Furthermore, published \n",
|
395 |
+
"studies indicated that achieving a response according to \n",
|
396 |
+
"RECIST criteria is predictive of remission and improved \n",
|
397 |
+
"survival in specific settings[11]. Conversely, both RECIST \n",
|
398 |
+
"and their revised 1.1 version assumed that an early \n",
|
399 |
+
"increase in tumor growth and/or the appearance of \n",
|
400 |
+
"new lesions correspond to progressive disease (PD), \n",
|
401 |
+
"testifying drug failure and indicating the need of ongoing \n",
|
402 |
+
"treatment cessation[10].\n",
|
403 |
+
"Some exceptions for the use of these criteria have \n",
|
404 |
+
"been already suggested in patients treated with target \n",
|
405 |
+
"therapies such as tyrosine kinase inhibitors as in this \n",
|
406 |
+
"group of patients the lack of tumor shrinkage in the \n",
|
407 |
+
"presence of a stable disease has been identified as a \n",
|
408 |
+
"potential surrogate end point for improved clinical out -\n",
|
409 |
+
"come[12]. However , in the clinical practice, the radiolo -\n",
|
410 |
+
"gical effect of immunotherapeutic agents has raised \n",
|
411 |
+
"several more relevant and complex challenges for \n",
|
412 |
+
"the determination of their imaging-based response at \n",
|
413 |
+
"single patient level[13]. In published studies, while some \n",
|
414 |
+
"patients have responded to ICPIs with the expected \n",
|
415 |
+
"tumor shrinkage (chemo-like response) or with a \n",
|
416 |
+
"stable disease (target therapy-like response), other \n",
|
417 |
+
"distinct immune-related patterns of response have been \n",
|
418 |
+
"identified. In particular , an initial increase in tumor size, \n",
|
419 |
+
"development of new lesions and then a delayed objective \n",
|
420 |
+
"response were also observed in patients treated with \n",
|
421 |
+
"immunotherapeutic agents[13]. Specifically, in some \n",
|
422 |
+
"patients, the initial increase in total tumor burden was \n",
|
423 |
+
"proven to be due to inflammatory cell infiltrates by \n",
|
424 |
+
"means of biopsy. In these patients the initial pseudo-\n",
|
425 |
+
"progression was followed by a decrease in tumor burden \n",
|
426 |
+
"or even disease regression. \n",
|
427 |
+
"As RECIST criteria were not suitable to catch \n",
|
428 |
+
"these atypical responses, the so-called immune-\n",
|
429 |
+
"related response criteria (irRC) have been proposed to \n",
|
430 |
+
"provide more rigorous characterization of all patterns \n",
|
431 |
+
"of response observed in the phase II development \n",
|
432 |
+
"program for ipilimumab in melanoma[13-15] (Table 1). \n",
|
433 |
+
"The main differences between RECIST 1.1 and irRC \n",
|
434 |
+
"rely on the fact that, according to irRC, appearance of \n",
|
435 |
+
"new lesions (PD according to the RECIST criteria) will \n",
|
436 |
+
"only result in progressive disease in case of a significant \n",
|
437 |
+
"(≥ 25%) increase in total tumor burden with respect \n",
|
438 |
+
"to baseline. Moreover, different from conventional \n",
|
439 |
+
"criteria, if irRC-based PD is evident, it requires further \n",
|
440 |
+
"confirmation after one month with the aim of capturing 29 February 28, 2017 |Volume 9 |Issue 2| WJR|www.wjgnet.comdelayed response.\n",
|
441 |
+
"Recent ly, Hodi et al[16] evaluated atypical response \n",
|
442 |
+
"patterns and reported the overall survival data in \n",
|
443 |
+
"correlation with irRC and RECIST criteria in the context \n",
|
444 |
+
"of a retrospective analysis of 327 melanoma patients \n",
|
445 |
+
"treated wit h the PD-1 inhibitor pembrolizumab. This \n",
|
446 |
+
"study indicated that the conventional RECIST asses sment \n",
|
447 |
+
"alone tends to underestimate the benefit of PD-1 inhibi -\n",
|
448 |
+
"tor therapy in a subset of patients, supporting a need of \n",
|
449 |
+
"immune-related response evaluation[16]. IrRC are thus \n",
|
450 |
+
"increasingly proposed, but they have not been validated \n",
|
451 |
+
"yet in the context of clinical trials and most trials \n",
|
452 |
+
"involving ICPIs continue to use RECIST 1.1 to obtain \n",
|
453 |
+
"standardized endpoints for regulatory approvals[15]. \n",
|
454 |
+
"However, although the irRC seem better than \n",
|
455 |
+
"RECIST , the former has some limitations. The irRC use \n",
|
456 |
+
"the bidimensional measurements in line with WHO \n",
|
457 |
+
"criteria that are now rarely used in clinical trials and \n",
|
458 |
+
"replaced by the unidimensional measurement of the larger axis of target lesions (RECIST 1.0 and 1.1). The \n",
|
459 |
+
"bidimensional measurements introduce a greater vari -\n",
|
460 |
+
"ability than unidimensional measurements and make it \n",
|
461 |
+
"difficult to compare the responses with studies using the \n",
|
462 |
+
"RECIST criteria.\n",
|
463 |
+
"Is there a role for 18F-fluoro-2-deoxy-D-glucose positron \n",
|
464 |
+
"emission tomography in the evaluation of ICPIs?\n",
|
465 |
+
"While optimal CT-based response criteria for ICPIs \n",
|
466 |
+
"are in the path of their identification, very few data \n",
|
467 |
+
"are available for the evaluation of immunotherapy by \n",
|
468 |
+
"means of 18F-fluoro-2-deoxy-D-glucose positron emi s-\n",
|
469 |
+
"sion tomography (18F-FDG-PET), one of the most used \n",
|
470 |
+
"imaging techniques in oncology. 18F-FDG-PET is currently \n",
|
471 |
+
"the most widely used molecular imaging mod ality in \n",
|
472 |
+
"the clinical practice for staging and restaging of several \n",
|
473 |
+
"cancers. 18F-FDG-PET is clinically indicated before and \n",
|
474 |
+
"after treatment in patients with Hod gkin’s lymphoma \n",
|
475 |
+
"and NSCLC and it is used in patients with melanoma for \n",
|
476 |
+
"Figure 1 Schematic representation of mechanism of action of nivolumab and ipilimumab, two Food and Drug Administration approved immune \n",
|
477 |
+
"checkpoint inhibitors. To prevent autoimmunity, numerous checkpoint pathways regulate the activation of T cells at multiple steps (process known as peripheral \n",
|
478 |
+
"tolerance). Central in this process are the cytotoxic T-lymphocyte-associated antigen 4 (CTLA-4) and programmed death 1 (PD-1) immune checkpoints pathways. \n",
|
479 |
+
"CTLA-4 is potentially able to stop autoreactive T cells at the initial stage of naive T-cell activation, typically in lymph nodes, while PD-1 regulates previously activated \n",
|
480 |
+
"T cells at the later stages of an immune response in peripheral tissues. The binding between T-cell receptor (TCR), which is expressed on T cell surface, with major \n",
|
481 |
+
"histocompatibility complex (MHC) expressed on antigen presenting cells (APCs) provides specificity to T-cell activation. However, T cell activation requires more than \n",
|
482 |
+
"one stimulatory signal. Among them a central role is played by the binding between B7 molecules (APC) with CD28 (T-Cell). CTLA-4 is a CD28 homolog which does \n",
|
483 |
+
"not produce a stimulatory signal but inhibits TCR-MHC binding and thus the T-Cell activation. Different from T-cells in which the amount of CTLA-4 is low, T-Regs \n",
|
484 |
+
"highly express CTLA-4. In these cells CTLA-4 might play a role in their suppressive functions. PD-1 is a member of the B7/CD38 family of protein, which is able to \n",
|
485 |
+
"bind with two different ligands: Programmed death ligand 1 (PD-L1) and programmed death ligand 2 (PD-L2). PD-1 activation in a T-cell prevents the phosphorylation \n",
|
486 |
+
"of key TCR signaling intermediates and thus T-cell activation, resulting in suboptimal control of infections and cancers. Therefore, even though they act at different \n",
|
487 |
+
"phases of T-cell activation, the negative effect of PD-1 and CTLA-4 on T-cell activity is similar. Moreover, different from CTLA-4, PD-1 expression is not specific in \n",
|
488 |
+
"T-cells, but can be observed also in B-cells and myeloid cells. The rationale for immune checkpoint inhibition (represented in red) for cancer treatment is that CTLA-4 \n",
|
489 |
+
"and PD1 pathways are strictly related to cancer survival and thus targeting these molecules or their ligands with monoclonal antibodies permits to impact on cancer \n",
|
490 |
+
"growth. Therefore, even if the exact mechanism of action of these monoclonal antibodies in the antitumor response remains unclear, research data suggest that it is at \n",
|
491 |
+
"least partially related to an activation and proliferation of T-cells regardless of TCR specificity (due to the inhibition of the inhibitory activity of these checkpoints), which \n",
|
492 |
+
"enhances the anti-cancer immune reaction. T Reg\n",
|
493 |
+
"Antigen\n",
|
494 |
+
"presenting cellCancer cell\n",
|
495 |
+
"T cellB7\n",
|
496 |
+
"B7\n",
|
497 |
+
"B7\n",
|
498 |
+
"B7\n",
|
499 |
+
"B7\n",
|
500 |
+
"B7\n",
|
501 |
+
"B7\n",
|
502 |
+
"B7MHCCTLA-4\n",
|
503 |
+
"TCR\n",
|
504 |
+
"IPILMUMABNIVOLUMAB\n",
|
505 |
+
"CD28\n",
|
506 |
+
"CD28\n",
|
507 |
+
"CD28\n",
|
508 |
+
"CTLA-4 CTLA-4PD-1\n",
|
509 |
+
"PD-1\n",
|
510 |
+
"PD-1PD-L1\n",
|
511 |
+
"PD-L1\n",
|
512 |
+
"PD-L1MHCMHCBauckneht M et al. Immune checkpoint inhibitors and PET30 February 28, 2017 |Volume 9 |Issue 2| WJR|www.wjgnet.comspecific clinical indications[17-19]. The use of 18F-FDG-PET \n",
|
513 |
+
"in post-treatment settings is based on the assumption \n",
|
514 |
+
"that tumor size changes are only the final step in a \n",
|
515 |
+
"sequence of complex metabolic and functional processes \n",
|
516 |
+
"during and after treatment[20]. Two different types \n",
|
517 |
+
"of criteria have been proposed for the identification \n",
|
518 |
+
"of 18F-FDG-PET-based response in solid tumors: The \n",
|
519 |
+
"European Organization for Research and Treatment of \n",
|
520 |
+
"Cancer (EORTC) and the PET Response Criteria in Solid \n",
|
521 |
+
"Tumors (PERCIST) criteria[21,22] (Table 1). Both criteria \n",
|
522 |
+
"target the most metabolically active part of patient’s \n",
|
523 |
+
"tumor burden, which is regarded as the most viable and \n",
|
524 |
+
"aggressive disease site. In both cases, the so-called \n",
|
525 |
+
"standardized uptake value (SUV) is measured at baseline \n",
|
526 |
+
"and after treatment. However , they differ for some rele -\n",
|
527 |
+
"vant aspects. The EORTC criteria were published in 1999 \n",
|
528 |
+
"and are based on the evaluation of a lesion-specific \n",
|
529 |
+
"region of interest (ROI) chosen as the most 18F-FDG-avid \n",
|
530 |
+
"at baseline and followed in the after-treatment scans[22]. \n",
|
531 |
+
"The PERCIST criteria were proposed in 2009 by Wahl \n",
|
532 |
+
"et al[21] and rely on the use of a 1 cm3 ROI on the most \n",
|
533 |
+
"18F-FDG-avid part of the single most metabolically active \n",
|
534 |
+
"lesion at each PET/CT scan (which is not necessarily \n",
|
535 |
+
"located in the same lesion in all scans). \n",
|
536 |
+
"Relatively few papers have compared the two methods in solid tumors and good agreement, similar \n",
|
537 |
+
"responses and survival outcomes have been highlighted \n",
|
538 |
+
"in the available studies[23]. However, for the EORTC \n",
|
539 |
+
"criteria, no recommendations on the number of target \n",
|
540 |
+
"lesions or on whether computing SUV max or average \n",
|
541 |
+
"SUV for response calculation are given while the \n",
|
542 |
+
"PERCIST criteria recommend the use of lean body mass \n",
|
543 |
+
"for SUV normalization (SUL). In this framework, some \n",
|
544 |
+
"studies have demonstrated a higher accuracy with \n",
|
545 |
+
"respect to RECIST for both metabolic response based \n",
|
546 |
+
"criteria in patients treated with target therapies such as \n",
|
547 |
+
"erlotinib. This finding is due to the relative lower tumor \n",
|
548 |
+
"shrinkage characterizing this type of treatment[24]. \n",
|
549 |
+
"Similarly, an 18F-FDG-PET-based five-point scale (5-PS), \n",
|
550 |
+
"the so-called Deauville criteria, has been demonstrated \n",
|
551 |
+
"to be superior to CT-based response by scoring images \n",
|
552 |
+
"in the assessment of response at the middle and end of \n",
|
553 |
+
"treatment in HD patients[18]. Again these findings testify \n",
|
554 |
+
"that functional changes always precede morphological \n",
|
555 |
+
"changes in the course of pathological processes. In \n",
|
556 |
+
"this regard it might be of interest to test the complex \n",
|
557 |
+
"and variegated response to ICPIs by means of PET-\n",
|
558 |
+
"based criteria. In fact, on one hand, functional imaging \n",
|
559 |
+
"may capture different features of treatment with ICPIs \n",
|
560 |
+
"in terms of entity and time course of response. On Category PERCIST EORTC 1999 RECIST 1.1 irRC\n",
|
561 |
+
" Target lesions The hottest single tumor lesion (SUL \n",
|
562 |
+
"peak) at baseline\n",
|
563 |
+
"18F-FDG PET The most 18F-FDG-avid \n",
|
564 |
+
"lesions (SUV BSA). Number \n",
|
565 |
+
"of lesions not specifiedMaximum, 5 Maximum, 15 lesions\n",
|
566 |
+
" New lesion Results in progressive disease at first \n",
|
567 |
+
"appearanceResults in progressive disease \n",
|
568 |
+
"at first appearanceResults in progressive \n",
|
569 |
+
"disease at first \n",
|
570 |
+
"appearanceUp to 10 new visceral and 5 cutaneous \n",
|
571 |
+
"lesions may be added to the sum of \n",
|
572 |
+
"the products of the two largest \n",
|
573 |
+
"perpendicular diameters of \n",
|
574 |
+
"all index lesions at \n",
|
575 |
+
"any time point\n",
|
576 |
+
" Complete \n",
|
577 |
+
" responseCMR: Complete resolution of 18F-FDG \n",
|
578 |
+
"uptake within the \n",
|
579 |
+
"target lesion (< mean liver activity and \n",
|
580 |
+
"indistinguishable from \n",
|
581 |
+
"background/blood pool and no new \n",
|
582 |
+
"18F-FDG-avid lesions)CMR: Complete absence of \n",
|
583 |
+
"18F-FDG uptake Disappearance of all target and nontarget lesions\n",
|
584 |
+
" Nodes must regress to < 10 mm short axis \n",
|
585 |
+
" No new lesions\n",
|
586 |
+
" Confirmation required\n",
|
587 |
+
" Partial response PMR: A reduction of a minimum of \n",
|
588 |
+
"30% in the target tumor 18F-\n",
|
589 |
+
"FDG SUL peakPMR: A decrease in SUV > \n",
|
590 |
+
"25%≥ 30% decrease in \n",
|
591 |
+
"tumor burden compared \n",
|
592 |
+
"to baseline \n",
|
593 |
+
"Confirmation required≥ 50% decrease in tumor burden \n",
|
594 |
+
"compared with baseline1\n",
|
595 |
+
"Confirmation required\n",
|
596 |
+
" Progressive \n",
|
597 |
+
" diseasePMD: A 30% increase in 18F-FDG SUL \n",
|
598 |
+
"peak or advent of new 18F-FDG-avid \n",
|
599 |
+
"lesionsPMD: An increase in SUV > \n",
|
600 |
+
"25% or appearance of new \n",
|
601 |
+
"lesions≥ 20% + 5 mm absolute \n",
|
602 |
+
"increase in tumor \n",
|
603 |
+
"burden compared\n",
|
604 |
+
"with nadir\n",
|
605 |
+
"Appearance of new \n",
|
606 |
+
"lesions or progression of \n",
|
607 |
+
"nontarget lesions≥ 25% increase in tumor burden \n",
|
608 |
+
"compared with baseline,\n",
|
609 |
+
"nadir or reset baseline1\n",
|
610 |
+
"New lesions added to tumor burden\n",
|
611 |
+
"Confirmation required\n",
|
612 |
+
" Stable disease SMD: Disease other than CMR, PMR \n",
|
613 |
+
"or PMDSMD: Increase in SUV by < \n",
|
614 |
+
"25% or decrease in SUV by < \n",
|
615 |
+
"15% Neither partial response nor progressive diseaseTable 1 Key features of positron emission tomography Response Criteria in Solid Tumors, European Organization for Research and \n",
|
616 |
+
"Treatment of Cancer 1999, Response Evaluation Criteria in Solid Tumors 1.1 and immune related Response Criteria \n",
|
617 |
+
"1If an increase in tumor burden is observed at the first scheduled assessment, the baseline is reset to the value observed at the first assessment. PERCIST: PET \n",
|
618 |
+
"Response Criteria in Solid Tumors; EORTC: European Organization for Research and Treatment of Cancer; RECIST: Response Evaluation Criteria in Solid \n",
|
619 |
+
"Tumors; irRC: Immune related Response Criteria; CMR: Complete metabolic response; PMR: Partial metabolic response; PMD: Progressive metabolic disease; \n",
|
620 |
+
"SMD: Stable metabolic disease; SUL: SUV normalized to lean body mass; SUV BSA: SUV normalized for body surface area; SUV: Standardized uptake value.Bauckneht M et al. Immune checkpoint inhibitors and PET31 February 28, 2017 |Volume 9 |Issue 2| WJR|www.wjgnet.comthe other hand, it has been reported that the initial \n",
|
621 |
+
"increase in tumor size, later followed by tumor volume \n",
|
622 |
+
"reduction in part of the patients treated with ICPIs, is \n",
|
623 |
+
"due to inflammatory cell infiltrates. Accordingly, given \n",
|
624 |
+
"the well-known high metabolic activity characterizing \n",
|
625 |
+
"inflammatory cells, this feature may also hamper the \n",
|
626 |
+
"evaluation of 18F-FDG-PET-based response to ICPIs. \n",
|
627 |
+
"Sachpekidis et al[20] evaluated the role of 18F-FDG-\n",
|
628 |
+
"PET/CT after two cycles of ipilimumab in predicting the \n",
|
629 |
+
"final response to therapy in 22 patients with metastatic \n",
|
630 |
+
"melanoma. They evaluated response to treatment by \n",
|
631 |
+
"means of the EORTC criteria and found that 18F-FDG-\n",
|
632 |
+
"PET/CT after two cycles of ipilimumab is predictive of \n",
|
633 |
+
"the final treatment outcome in patients with progressive \n",
|
634 |
+
"metabolic disease (PMD) and stable metabolic disease \n",
|
635 |
+
"(SMD)[20]. However, two patients were initially falsely \n",
|
636 |
+
"classified as early SMD, but they later demonstrated \n",
|
637 |
+
"new metastatic lesions, “upgrading” them to late PMD. \n",
|
638 |
+
"Similarly, early evaluation by means of 18F-FDG-PET \n",
|
639 |
+
"did not identify responders to treatment as the two \n",
|
640 |
+
"patients eventually characterized with PMR were initially \n",
|
641 |
+
"classified with early PMD due to new lesions[20]. In fact, \n",
|
642 |
+
"both RECIST 1.1 and PET-based criteria consider the \n",
|
643 |
+
"identification of new (metabolically active) lesions as \n",
|
644 |
+
"progressive disease. Therefore, presently proposed \n",
|
645 |
+
"PET-based metabolic criteria suffer from at least one \n",
|
646 |
+
"of the same limitations that ha ve resulted in the under -\n",
|
647 |
+
"estimation of response to treatment with ICPIs by \n",
|
648 |
+
"means of RECIST 1.1. Similarly, in the phase 2 study \n",
|
649 |
+
"by Younes et al[9], nivolumab resulted in frequent res -\n",
|
650 |
+
"ponses in patients with classical Hodgkin’s lymphoma \n",
|
651 |
+
"after failure of ASCT and brentuximab vedotin. Most of \n",
|
652 |
+
"these responses were maintained through the reported \n",
|
653 |
+
"follow-up period with an acceptable safety profile. In \n",
|
654 |
+
"this study 18F-FDG-PET was performed at baseline and \n",
|
655 |
+
"at weeks 17 and 25. A negative 18F-FDG-PET scan, \n",
|
656 |
+
"visually assessed by an independent radiological review \n",
|
657 |
+
"committee (IRRC), was required for confirmation \n",
|
658 |
+
"of complete remission. The study demonstrated a \n",
|
659 |
+
"general reduction of tumor burden. Yet, discordance \n",
|
660 |
+
"in complete remission between IRRC and investigator \n",
|
661 |
+
"assessments was largely based on the interpretation \n",
|
662 |
+
"of 18F-FDG-PET scans and standardized uptake values \n",
|
663 |
+
"were not collected as part of this study. The vast \n",
|
664 |
+
"majority of other available data on the potential utility \n",
|
665 |
+
"of 18F-FDG-PET afte r ICPIs are case reports more \n",
|
666 |
+
"often describing underlying challenges of monitoring \n",
|
667 |
+
"radiologic response in these patients and showing \n",
|
668 |
+
"18F-FDG-PET features of inflammatory reactions. PET-\n",
|
669 |
+
"highlighted autoimmune pancolitis, splenic sarcoidosis-\n",
|
670 |
+
"like lesion and exacerbation of sarcoidosis as a potential \n",
|
671 |
+
"confounder in the assessment of tumor response in \n",
|
672 |
+
"a melanoma patient treated with ipilimumab have all \n",
|
673 |
+
"been described[25-27]. Similarly, K oo et al[26] illustrated a \n",
|
674 |
+
"series of inflammatory reactions with avid FDG uptake \n",
|
675 |
+
"in patients treated with ipilimumab, including those with \n",
|
676 |
+
"thyroiditis, hypophysitis, granulomatous inflammation in \n",
|
677 |
+
"the lymph nodes and skin, and enterocolitis. \n",
|
678 |
+
"Accordingly, the potential and challenges of 18F-FDG-PET imaging in the evaluation of patients treated with \n",
|
679 |
+
"ICPIs still need to be clarified and deeply addressed. \n",
|
680 |
+
"Given the relatively greater experience of CT-based \n",
|
681 |
+
"evaluation in this setting and the fact that irRC CT-\n",
|
682 |
+
"based criteria seem to better in capturing response to \n",
|
683 |
+
"ICPIs, it is worthwhile to ask if a similar modification of \n",
|
684 |
+
"PET-based criteria could be proposed in the future.\n",
|
685 |
+
"Potential new PET-based approaches to evaluate the \n",
|
686 |
+
"effect of ICPIs\n",
|
687 |
+
"As mentioned above, due to its intrinsic nature, 18F-FDG-\n",
|
688 |
+
"PET displays not only cancer cell’s metabolic activity but \n",
|
689 |
+
"also inflammation. Since the antineoplastic activity of \n",
|
690 |
+
"ICPIs is highly related to the activation of T cells against \n",
|
691 |
+
"cancer cells, 18F-FDG accumulation might cause false-\n",
|
692 |
+
"positive findings. Yet, discrimination between benign \n",
|
693 |
+
"and malignant processes represents a huge challenge \n",
|
694 |
+
"for 18F-FDG-PET in this clinical setting. Together with the \n",
|
695 |
+
"need of the clinicians to discriminate between responders \n",
|
696 |
+
"and non-responders, allowing individual therapy \n",
|
697 |
+
"optimization and avoiding adverse effects brought \n",
|
698 |
+
"about by ineffective therapy, several studies have been \n",
|
699 |
+
"recently conducted to explore the possible role of non-\n",
|
700 |
+
"FDG radiotracers in the field of ICPIs. These studies, \n",
|
701 |
+
"mainly performed with labeled monoclonal antibodies, \n",
|
702 |
+
"open the new era of the so-called “Immuno-PET”. \n",
|
703 |
+
"Accordingly, in 2014, Higashikawa et al[28] developed \n",
|
704 |
+
"a molecular imaging probe that is able to evaluate \n",
|
705 |
+
"CTLA-4 expression prior to CTLA-4 targeting in cancer . \n",
|
706 |
+
"This 64Cu labeled radiotracer is basically composed \n",
|
707 |
+
"of DOTA protein together with a CTLA-4 specific \n",
|
708 |
+
"antibody and is able to display CTLA-4 expression \n",
|
709 |
+
"in vivo . Similarly, specific experimental radiotracers \n",
|
710 |
+
"were proposed for the visualization of PD-1 and PD-L1 \n",
|
711 |
+
"cellular expression[29-32]. Maute et al[29] measured PD-L1 \n",
|
712 |
+
"expression by radiolabeling a PD-L1 high affinity protein \n",
|
713 |
+
"(HAC) with 64Cu and tested its feasibility in a living \n",
|
714 |
+
"mouse, while Hettich et al[30] developed two 64Cu labeled \n",
|
715 |
+
"immunoPET tracers for imaging of both PD-1 and PD-L1. \n",
|
716 |
+
"Also one SPECT study with radiolabeled anti-murine \n",
|
717 |
+
"PD-L1 in mice has been conducted[32]. More recently, a \n",
|
718 |
+
"89Zr labeled CD3 PET imaging agent was proposed by \n",
|
719 |
+
"Larimer et al[33]. CD3 is a part of the TCR complex that \n",
|
720 |
+
"serves as a global T lymphocyte marker . By serving as \n",
|
721 |
+
"a marker of total T-cell infiltration, CD3 may represent \n",
|
722 |
+
"a more direct approach than pre-treatment biopsy \n",
|
723 |
+
"or genetic screening to monitoring tumor immune \n",
|
724 |
+
"response, by directly examining active recruitment of \n",
|
725 |
+
"T cells responsible for cancer cell death. In this study \n",
|
726 |
+
"the authors showed that CD3 PET imaging revealed \n",
|
727 |
+
"two distinct groups of mice, stratified by PET signal \n",
|
728 |
+
"intensity. While high-CD3 PET uptake was correlated \n",
|
729 |
+
"with subsequent reduced tumor volume, low uptake was \n",
|
730 |
+
"predictive of suboptimal response. Altogether these non-\n",
|
731 |
+
"invasive approaches allow simultaneous imaging of the \n",
|
732 |
+
"entire cancer mass and associated metastases, which \n",
|
733 |
+
"may differ from the primary tumor in CTLA-4, PD-1 or \n",
|
734 |
+
"PD-L1 expression status. Immune imaging can be used \n",
|
735 |
+
"for repeated assessment of the same tumor at different Bauckneht M et al. Immune checkpoint inhibitors and PET32 February 28, 2017 |Volume 9 |Issue 2| WJR|www.wjgnet.comtime points ( e.g., before and after treatment), thereby \n",
|
736 |
+
"yielding a richer set of diagnostic information that would \n",
|
737 |
+
"be difficult or impossible to achieve with traditional \n",
|
738 |
+
"approaches. Furthermore, although further investigations \n",
|
739 |
+
"are needed before their potential introduction in the \n",
|
740 |
+
"clinical setting, these non-invasive immune-diagnostic \n",
|
741 |
+
"approaches might yield novel insights into the biology \n",
|
742 |
+
"and pathophysiological importance of ICPIs as cancer \n",
|
743 |
+
"therapeutics. \n",
|
744 |
+
"REFERENCES\n",
|
745 |
+
"1 Pardoll DM . The blockade of immune checkpoints in cancer \n",
|
746 |
+
"immunotherapy. Nat Rev Cancer 2012; 12: 252-264 [PMID: \n",
|
747 |
+
"22437870 DOI: 10.1038/nrc3239]\n",
|
748 |
+
"2 Walunas TL , Lenschow DJ, Bakker CY , Linsley PS, Freeman GJ, \n",
|
749 |
+
"Green JM, Thompson CB, Bluestone JA. CTLA-4 can function as \n",
|
750 |
+
"a negative regulator of T cell activation. Immunity 1994; 1: 405-413 \n",
|
751 |
+
"[PMID: 7882171]\n",
|
752 |
+
"3 Francisco LM , Sage PT, Sharpe AH. The PD-1 pathway in \n",
|
753 |
+
"tolerance and autoimmunity. Immunol Rev 2010; 236: 219-242 \n",
|
754 |
+
"[PMID: 20636820 DOI: 10.1111/j.1600]\n",
|
755 |
+
"4 O’Day SJ , Hamid O, Urba WJ. Targeting cytotoxic T-lymphocyte \n",
|
756 |
+
"antigen-4 (CTLA-4): a novel strategy for the treatment of melanoma \n",
|
757 |
+
"and other malignancies. Cancer 2007; 110: 2614-2627 [PMID: \n",
|
758 |
+
"18000991 DOI: 10.1002/cncr.23086]\n",
|
759 |
+
"5 Hodi FS , O’Day SJ, McDermott DF, Weber RW, Sosman JA, \n",
|
760 |
+
"Haanen JB, Gonzalez R, Robert C, Schadendorf D, Hassel JC, \n",
|
761 |
+
"Akerley W, van den Eertwegh AJ, Lutzky J, Lorigan P, Vaubel JM, \n",
|
762 |
+
"Linette GP, Hogg D, Ottensmeier CH, Lebbé C, Peschel C, Quirt \n",
|
763 |
+
"I, Clark JI, Wolchok JD, Weber JS, Tian J, Yellin MJ, Nichol GM, \n",
|
764 |
+
"Hoos A, Urba WJ. Improved survival with ipilimumab in patients \n",
|
765 |
+
"with metastatic melanoma. N Engl J Med 2010; 363: 711-723 \n",
|
766 |
+
"[PMID: 20525992 DOI: 10.1056/NEJMoa1003466]\n",
|
767 |
+
"6 Giri A , Walia SS, Gajra A. Clinical Trials Investigating Immune \n",
|
768 |
+
"Checkpoint Inhibitors in Non-Small-Cell Lung Cancer. Rev Recent \n",
|
769 |
+
"Clin Trials 2016; 11: 297-305 [PMID: 27457350]\n",
|
770 |
+
"7 Carlo MI , V oss MH, Motzer RJ. Checkpoint inhibitors and other \n",
|
771 |
+
"novel immunotherapies for advanced renal cell carcinoma. Nat \n",
|
772 |
+
"Rev Urol 2016; 13: 420-431 [PMID: 27324121 DOI: 10.1038/\n",
|
773 |
+
"nrurol.2016.103]\n",
|
774 |
+
"8 Ball MW , Allaf ME, Drake CG. Recent advances in immuno -\n",
|
775 |
+
"therapy for kidney cancer. Discov Med 2016; 21: 305-313 [PMID: \n",
|
776 |
+
"27232516]\n",
|
777 |
+
"9 Younes A , Santoro A, Shipp M, Zinzani PL, Timmerman JM, Ansell \n",
|
778 |
+
"S, Armand P, Fanale M, Ratanatharathorn V , Kuruvilla J, Cohen JB, \n",
|
779 |
+
"Collins G, Savage KJ, Trneny M, Kato K, Farsaci B, Parker SM, \n",
|
780 |
+
"Rodig S, Roemer MG, Ligon AH, Engert A. Nivolumab for classical \n",
|
781 |
+
"Hodgkin’s lymphoma after failure of both autologous stem-cell \n",
|
782 |
+
"transplantation and brentuximab vedotin: a multicentre, multicohort, \n",
|
783 |
+
"single-arm phase 2 trial. Lancet Oncol 2016; 17: 1283-1294 [PMID: \n",
|
784 |
+
"27451390 DOI: 10.1016/S1470-2045(16)30167]\n",
|
785 |
+
"10 Eisenhauer EA , Therasse P, Bogaerts J, Schwartz LH, Sargent D, \n",
|
786 |
+
"Ford R, Dancey J, Arbuck S, Gwyther S, Mooney M, Rubinstein \n",
|
787 |
+
"L, Shankar L, Dodd L, Kaplan R, Lacombe D, Verweij J. New \n",
|
788 |
+
"response evaluation criteria in solid tumours: revised RECIST \n",
|
789 |
+
"guideline (version 1.1). Eur J Cancer 2009; 45: 228-247 [PMID: \n",
|
790 |
+
"19097774 DOI: 10.1016/j.ejca.2008.10.026]\n",
|
791 |
+
"11 von Minckwitz G , Sinn HP, Raab G, Loibl S, Blohmer JU, \n",
|
792 |
+
"Eidtmann H, Hilfrich J, Merkle E, Jackisch C, Costa SD, Caputo \n",
|
793 |
+
"A, Kaufmann M. Clinical response after two cycles compared \n",
|
794 |
+
"to HER2, Ki-67, p53, and bcl-2 in independently predicting a \n",
|
795 |
+
"pathological complete response after preoperative chemotherapy in \n",
|
796 |
+
"patients with operable carcinoma of the breast. Breast Cancer Res \n",
|
797 |
+
"2008; 10: R30 [PMID: 18380893 DOI: 10.1186/bcr1989]\n",
|
798 |
+
"12 Tsujino K , Shiraishi J, Tsuji T, Kurata T, Kawaguchi T, Kubo \n",
|
799 |
+
"A, Takada M. Is response rate increment obtained by molecular \n",
|
800 |
+
"targeted agents related to survival benefit in the phase III trials of advanced cancer? Ann Oncol 2010; 21: 1668-1674 [PMID: \n",
|
801 |
+
"20064832 DOI: 10.1093/annonc/mdp588]\n",
|
802 |
+
"13 Wolchok JD , Hoos A, O’Day S, Weber JS, Hamid O, Lebbé C, \n",
|
803 |
+
"Maio M, Binder M, Bohnsack O, Nichol G, Humphrey R, Hodi FS. \n",
|
804 |
+
"Guidelines for the evaluation of immune therapy activity in solid \n",
|
805 |
+
"tumors: immune-related response criteria. Clin Cancer Res 2009; \n",
|
806 |
+
"15: 7412-7420 [PMID: 19934295 DOI: 10.1158/1078-0432]\n",
|
807 |
+
"14 Hodi FS , Sznol M, Kluger HM, McDermott DF, Carvajal RD, \n",
|
808 |
+
"Lawrence DP, Topalian SL, Atkins MB, Powderly JD, Sharfman \n",
|
809 |
+
"WH, Puzanov I, Smith DC, Leming PD, Lipson EJ, Taube JM, \n",
|
810 |
+
"Anders R, Horak CE, Kollia G, Gupta AK, Sosman JA. Long \n",
|
811 |
+
"term survival of ipilimumab-naive patients (pts) with advanced \n",
|
812 |
+
"melanoma (MEL) treated with nivolumab (anti-PD-1, BMS-936558, \n",
|
813 |
+
"ONO-4538) in a phase I trial. ASCO Annual Meeting 2014 May \n",
|
814 |
+
"30- Jun 3; Chicago, Illinois, USA. J Clin Oncol 2014; 32: 5s (suppl; \n",
|
815 |
+
"abstr 9002)\n",
|
816 |
+
"15 Chiou VL , Burotto M. Pseudoprogression and Immune-Related \n",
|
817 |
+
"Response in Solid Tumors. J Clin Oncol 2015; 33: 3541-3543 \n",
|
818 |
+
"[PMID: 26261262 DOI: 10.1200/JCO.2015.61.6870]\n",
|
819 |
+
"16 Hodi FS , Hwu WJ, Kefford R, Weber JS, Daud A, Hamid O, \n",
|
820 |
+
"Patnaik A, Ribas A, Robert C, Gangadhar TC, Joshua AM, Hersey P, \n",
|
821 |
+
"Dronca R, Joseph R, Hille D, Xue D, Li XN, Kang SP , Ebbinghaus S, \n",
|
822 |
+
"Perrone A, Wolchok JD. Evaluation of Immune-Related Response \n",
|
823 |
+
"Criteria and RECIST v1.1 in Patients With Advanced Melanoma \n",
|
824 |
+
"Treated With Pembrolizumab. J Clin Oncol 2016; 34: 1510-1517 \n",
|
825 |
+
"[PMID: 26951310 DOI: 10.1200/JCO.2015.64.0391]\n",
|
826 |
+
"17 Gould MK , Donington J, Lynch WR, Mazzone PJ, Midthun DE, \n",
|
827 |
+
"Naidich DP, Wiener RS. Evaluation of individuals with pulmonary \n",
|
828 |
+
"nodules: when is it lung cancer? Diagnosis and management of lung \n",
|
829 |
+
"cancer, 3rd ed: American College of Chest Physicians evidence-\n",
|
830 |
+
"based clinical practice guidelines. Chest 2013; 143: e93S-120S \n",
|
831 |
+
"[PMID: 23649456 DOI: 10.1378/chest.12-2351]\n",
|
832 |
+
"18 Cheson BD , Fisher RI, Barrington SF, Cavalli F, Schwartz LH, \n",
|
833 |
+
"Zucca E, Lister TA. Recommendations for initial evaluation, \n",
|
834 |
+
"staging, and res ponse assessment of Hodgkin and non-Hodgkin \n",
|
835 |
+
"lymphoma: the Lugano classification. J Clin Oncol 2014; 32: \n",
|
836 |
+
"3059-3068 [PMID: 25113753 DOI: 10.1200/JCO.2013.54.8800]\n",
|
837 |
+
"19 Morbelli S , Capitanio S, De Carli F, Bongioanni F, De Astis E, \n",
|
838 |
+
"Miglino M, Verardi MT, Buschiazzo A, Fiz F, Marini C, Pomposelli \n",
|
839 |
+
"E, Sambuceti G. Baseline and ongoing PET-derived factors predict \n",
|
840 |
+
"detrimental effect or potential utility of 18F-FDG PET/CT (FDG-\n",
|
841 |
+
"PET/CT) performed f or surveillance in asymptomatic lymphoma \n",
|
842 |
+
"patients in first remission. Eur J Nucl Med Mol Imaging 2016; 43: \n",
|
843 |
+
"232-239 [PMID: 26283504 DOI: 10.1007/s00259-015-3164-9]\n",
|
844 |
+
"20 Sachpekidis C , Larribere L, Pan L, Haberkorn U, Dimitrakopoulou-\n",
|
845 |
+
"Strauss A, Hassel JC. Predictive value of early 18F-FDG PET/\n",
|
846 |
+
"CT studies for treatment response evaluation to ipilimumab in \n",
|
847 |
+
"metastatic melanoma: preliminary results of an ongoing study. Eur J \n",
|
848 |
+
"Nucl Med Mol Imaging 2015; 42: 386-396 [PMID: 25359635 DOI: \n",
|
849 |
+
"10.1007/s00259-014-2944-y]\n",
|
850 |
+
"21 Wahl RL , Jacene H, Kasam on Y, Lodge MA. From RECIST to \n",
|
851 |
+
"PERCIST: Evolving Considerations for PET response criteria in \n",
|
852 |
+
"solid tumors. J Nucl M ed 2009; 50 Suppl 1: 122S-150S [PMID: \n",
|
853 |
+
"19403881 DOI: 10.2967/jnumed.108.057307]\n",
|
854 |
+
"22 Young H , Baum R, Cremerius U, Herholz K, Hoekstra O, \n",
|
855 |
+
"Lammertsma AA, Pruim J, Price P. Measurement of clinical and \n",
|
856 |
+
"subclinical tumour response using [18F]-fluorodeoxyglucose \n",
|
857 |
+
"and positron emission tomography: review and 1999 EORTC \n",
|
858 |
+
"recommendations. European Organization for Research and \n",
|
859 |
+
"Treatment of Cancer (EORTC) PET Study Group. Eur J Cancer \n",
|
860 |
+
"1999; 35: 1773-1782 [PMID: 10673991]\n",
|
861 |
+
"23 Skougaard K , Nielsen D, Jensen BV , Hendel HW. Comparison of \n",
|
862 |
+
"EORTC criteria and PERCIST for PET/CT response evaluation of \n",
|
863 |
+
"patients with metastatic colorectal cancer treated with irinotecan \n",
|
864 |
+
"and cetuximab. J Nucl Med 2013; 54: 1026-1031 [PMID: 23572497 \n",
|
865 |
+
"DOI: 10.2967/jnumed.112.111757]\n",
|
866 |
+
"24 Stefano A , Russo G, Ippolito M, Cosentino S, Murè G, Baldari S, \n",
|
867 |
+
"Sabini MG, Sardina D, Valastro LM, Bordonaro R, Messa C, Gilardi \n",
|
868 |
+
"MC, Soto Parra H. Evaluation of erlotinib treatment response in \n",
|
869 |
+
"non-small cell lung cancer using metabolic and anatomic criteria. Q Bauckneht M et al. Immune checkpoint inhibitors and PET33 February 28, 2017 |Volume 9 |Issue 2| WJR|www.wjgnet.comJ Nucl Med Mol Imaging 2014 May 9; Epub ahead of print [PMID: \n",
|
870 |
+
"24809275]\n",
|
871 |
+
"25 Goethals L , Wilgenhof S, De Geeter F, Everaert H, Neyns B. \n",
|
872 |
+
"18F-FDG PET/CT imaging of an anti-CTLA-4 antibody-associated \n",
|
873 |
+
"autoimmune pancolitis. Eur J Nucl Med Mol Imaging 2011; 38: \n",
|
874 |
+
"1390-1391 [PMID: 21365253 DOI: 10.1007/s00259-011-1749-5]\n",
|
875 |
+
"26 Koo PJ , Klingensmith WC, Lewis KD, Bagrosky BM, Gonzalez \n",
|
876 |
+
"R. Anti-CTLA4 antibody therapy related complications on FDG \n",
|
877 |
+
"PET/CT. Clin Nucl Med 2014; 39: e93-e96 [PMID: 23657138 DOI: \n",
|
878 |
+
"10.1097/RLU.0b013e318292a775]\n",
|
879 |
+
"27 Perng P , Marcus C, Subramaniam RM. (18)F-FDG PET/CT and \n",
|
880 |
+
"Melanoma: Staging, Immune Modulation and Mutation-Targeted \n",
|
881 |
+
"Therapy Assessment, and Prognosis. AJR Am J Roentgenol 2015; \n",
|
882 |
+
"205: 259-270 [PMID: 26204273 DOI: 10.2214/AJR.14.13575]\n",
|
883 |
+
"28 Higashikawa K , Yagi K, Watanabe K, Kamino S, Ueda M, \n",
|
884 |
+
"Hiromura M, Enomoto S. 64Cu-DOTA-anti-CTLA-4 mAb enabled \n",
|
885 |
+
"PET visualization of CTLA-4 on the T-cell infiltrating tumor tissues. \n",
|
886 |
+
"PLoS One 2014; 9: e109866 [PMID: 25365349]\n",
|
887 |
+
"29 Maute RL , Gordon SR, Mayer AT, McCracken MN, Natarajan A, \n",
|
888 |
+
"Ring NG, Kimura R, Tsai JM, Manglik A, Kruse AC, Gambhir SS, \n",
|
889 |
+
"Weissman IL, Ring AM. Engineering high-affinity PD-1 variants for optimized immunotherapy and immuno-PET imaging. Proc Natl \n",
|
890 |
+
"Acad Sci USA 2015; 112: E6506-E6514 [PMID: 26604307 DOI: \n",
|
891 |
+
"10.1073/pnas.1519623112]\n",
|
892 |
+
"30 Hettich M , Braun F, Bartholomä MD, Schirmbeck R, Niedermann \n",
|
893 |
+
"G. High-Resolution PET Imaging with Therapeutic Antibody-based \n",
|
894 |
+
"PD-1/PD-L1 Checkpoint Tracers. Theranostics 2016; 6: 1629-1640 \n",
|
895 |
+
"[PMID: 27446497 DOI: 10.7150/thno.15253]\n",
|
896 |
+
"31 Heskamp S , Hobo W, Molkenboer-Kuenen JD, Olive D, Oyen WJ, \n",
|
897 |
+
"Dolstra H, Boerman OC. Noninvasive Imaging of Tumor PD-L1 \n",
|
898 |
+
"Expression Using Radiolabeled Anti-PD-L1 Antibodies. Cancer Res \n",
|
899 |
+
"2015; 75: 2928-2936 [PMID: 25977331 DOI: 10.1158/0008-5472.\n",
|
900 |
+
"CAN-14-3477]\n",
|
901 |
+
"32 Josefsson A , Nedrow JR, Park S, Banerjee SR, Rittenbach A, Jammes \n",
|
902 |
+
"F, Tsui B, Sgouros G. Imaging, Biodistribution, and Dosimetry of \n",
|
903 |
+
"Radionuclide-Labeled PD-L1 Antibody in an Immunocompetent \n",
|
904 |
+
"Mouse Model of Breast Cancer. Cancer Res 2016; 76: 472-479 [PMID: \n",
|
905 |
+
"26554829 DOI: 10.1158/0008-5472.CAN-15-2141]\n",
|
906 |
+
"33 Larimer BM , Wehrenberg-Klee E, Caraballo A, Mahmood U. \n",
|
907 |
+
"Quantitative CD3 PET Imaging Predicts Tumor Growth Response \n",
|
908 |
+
"to Anti-CTLA-4 Therapy. J Nucl Med 2016; 57: 1607-1611 [PMID: \n",
|
909 |
+
"27230929 DOI: 10.2967/jnumed.116.173930]\n",
|
910 |
+
"P- Reviewer : Morris DLL, Palumbo B S- Editor : Ji FF \n",
|
911 |
+
"L- Editor : Wang TQ E- Editor : Wu HLBauckneht M et al. Immune checkpoint inhibitors and PET\n",
|
912 |
+
" © 2017 Baishideng Publishing Group Inc . All rights reserved.Published by Baishideng Publishing Group Inc\n",
|
913 |
+
"8226 Regency Drive, Pleasanton, CA 94588, USA\n",
|
914 |
+
"Telephone: +1-925-223-8242\n",
|
915 |
+
"Fax: +1-925-223-8243\n",
|
916 |
+
"E-mail: bpgoffice@wjgnet.com\n",
|
917 |
+
"Help Desk: http://www.wjgnet.com/esps/helpdesk.aspx\n",
|
918 |
+
"http://www.wjgnet.com\n",
|
919 |
+
"\n"
|
920 |
+
]
|
921 |
+
}
|
922 |
+
],
|
923 |
+
"source": [
|
924 |
+
"print(text)"
|
925 |
+
]
|
926 |
+
},
|
927 |
+
{
|
928 |
+
"cell_type": "code",
|
929 |
+
"execution_count": 48,
|
930 |
+
"metadata": {},
|
931 |
+
"outputs": [],
|
932 |
+
"source": [
|
933 |
+
"import xml.etree.ElementTree as ET\n",
|
934 |
+
"tree = ET.parse(\"ijms-24-05988.nxml\")"
|
935 |
+
]
|
936 |
+
},
|
937 |
+
{
|
938 |
+
"cell_type": "code",
|
939 |
+
"execution_count": 49,
|
940 |
+
"metadata": {},
|
941 |
+
"outputs": [],
|
942 |
+
"source": [
|
943 |
+
"root = tree.getroot()\n",
|
944 |
+
"body_elements = root.findall(\".//body//p\")\n"
|
945 |
+
]
|
946 |
+
},
|
947 |
+
{
|
948 |
+
"cell_type": "code",
|
949 |
+
"execution_count": 51,
|
950 |
+
"metadata": {},
|
951 |
+
"outputs": [
|
952 |
+
{
|
953 |
+
"data": {
|
954 |
+
"text/plain": [
|
955 |
+
"'acute myeloid leukemia'"
|
956 |
+
]
|
957 |
+
},
|
958 |
+
"execution_count": 51,
|
959 |
+
"metadata": {},
|
960 |
+
"output_type": "execute_result"
|
961 |
+
}
|
962 |
+
],
|
963 |
+
"source": [
|
964 |
+
"root.findall(\".//kwd\")[0].text"
|
965 |
+
]
|
966 |
+
},
|
967 |
+
{
|
968 |
+
"cell_type": "code",
|
969 |
+
"execution_count": 53,
|
970 |
+
"metadata": {},
|
971 |
+
"outputs": [
|
972 |
+
{
|
973 |
+
"data": {
|
974 |
+
"text/plain": [
|
975 |
+
"'Variation in Lipid Species Profiles among Leukemic Cells Significantly Impacts Their Sensitivity to the Drug Targeting of Lipid Metabolism and the Prognosis of AML Patients'"
|
976 |
+
]
|
977 |
+
},
|
978 |
+
"execution_count": 53,
|
979 |
+
"metadata": {},
|
980 |
+
"output_type": "execute_result"
|
981 |
+
}
|
982 |
+
],
|
983 |
+
"source": [
|
984 |
+
"root.find(\".//article-title\").text"
|
985 |
+
]
|
986 |
+
},
|
987 |
+
{
|
988 |
+
"cell_type": "code",
|
989 |
+
"execution_count": 54,
|
990 |
+
"metadata": {},
|
991 |
+
"outputs": [
|
992 |
+
{
|
993 |
+
"data": {
|
994 |
+
"text/plain": [
|
995 |
+
"'Several studies have linked bad prognoses of acute myeloid leukemia (AML) to the ability of leukemic cells to reprogram their metabolism and, in particular, their lipid metabolism. In this context, we performed “in-depth” characterization of fatty acids (FAs) and lipid species in leukemic cell lines and in plasma from AML patients. We firstly showed that leukemic cell lines harbored significant differences in their lipid profiles at steady state, and that under nutrient stress, they developed common mechanisms of protection that led to variation in the same lipid species; this highlights that the remodeling of lipid species is a major and shared mechanism of adaptation to stress in leukemic cells. We also showed that sensitivity to etomoxir, which blocks fatty acid oxidation (FAO), was dependent on the initial lipid profile of cell lines, suggesting that only a particular “lipidic phenotype” is sensitive to the drug targeting of FAO. We then showed that the lipid profiles of plasma samples from AML patients were significantly correlated with the prognosis of patients. In particular, we highlighted the impact of phosphocholine and phosphatidyl-choline metabolism on patients’ survival. In conclusion, our data show that balance between lipid species is a phenotypic marker of the diversity of leukemic cells that significantly influences their proliferation and resistance to stress, and thereby, the prognosis of AML patients.'"
|
996 |
+
]
|
997 |
+
},
|
998 |
+
"execution_count": 54,
|
999 |
+
"metadata": {},
|
1000 |
+
"output_type": "execute_result"
|
1001 |
+
}
|
1002 |
+
],
|
1003 |
+
"source": [
|
1004 |
+
"root.find(\".//abstract/p\").text "
|
1005 |
+
]
|
1006 |
+
},
|
1007 |
+
{
|
1008 |
+
"cell_type": "code",
|
1009 |
+
"execution_count": 33,
|
1010 |
+
"metadata": {},
|
1011 |
+
"outputs": [],
|
1012 |
+
"source": [
|
1013 |
+
"\n",
|
1014 |
+
"content = \"\\n\".join([p.text for p in body_elements if p.text]) if body_elements else \"No Content Available\"\n"
|
1015 |
+
]
|
1016 |
+
},
|
1017 |
+
{
|
1018 |
+
"cell_type": "code",
|
1019 |
+
"execution_count": 31,
|
1020 |
+
"metadata": {},
|
1021 |
+
"outputs": [
|
1022 |
+
{
|
1023 |
+
"name": "stdout",
|
1024 |
+
"output_type": "stream",
|
1025 |
+
"text": [
|
1026 |
+
"School nutrition is an important key modifier in terms of child and adolescent nutrient intake. A poor diet early in life can lead to a multitude of immediate and long-term health problems. Changes to the dietary information given in the school setting as well as changes to the food programs offered have the potential to promote adherence to a healthy diet, which can lead to lifelong health benefits.\n",
|
1027 |
+
"The present Special Issue includes two multicomponent school-based nutrition interventions to increase fruit (F) and vegetable (V) intake in children [\n",
|
1028 |
+
"Both multicomponent school-based interventions in the present Special Issue used the well-known theory of planned behavior as one of their theoretical models. More specifically, attitude, subjective norms, and perceived behavioral control can predict behavioral intentions. This approach has effectively predicted and changed diet-related behaviors and intentions in youth [\n",
|
1029 |
+
"The Dutch “Kokkerelli learning street” program combined the classroom with experiential learning strategies. Using this program, Hahnraths et al. also examined FV preferences, knowledge, attitudes, and intention to consume FV short term (directly after the intervention) and after 3 months [\n",
|
1030 |
+
"Both of the school-based nutrition interventions included in the Special Issue aimed to change F and V intake in children aged approximately 7–10 years old. Preference is the main factor associated with F and V in children, whereas Vs are not an innate preferred food. However, participation in the 3-year intervention had a stronger effect on changing FV intake than change in FV preference among primary school children [\n",
|
1031 |
+
"Overall, these intervention studies [\n",
|
1032 |
+
"It is noteworthy that the 3-year intervention “Nutri-skolica” was not fully implemented due to the COVID-19 pandemic. On the same note, the next study in the Special Issue examines the changes due to COVID-19 regarding the implementation of emergency school meals and pandemic electronic benefits in an urban setting. Cadenhead et al. presented qualitative data on facilitators and barriers to using the available emergency school meals and the P-EBT [\n",
|
1033 |
+
"Through the Hunger-Free Kids Act of 2010, the US Department of Agriculture (USDA) established policies to improve the nutritional quality of food and beverages served to US children through federal food assistance programs and it made changes in the Child and Adult Care Food Program (CAFP). In this Special Issue, Dave et al. assess what changes in children’s dietary behaviors occurred as a result of the new CACFP meal pattern requirements [\n",
|
1034 |
+
"The Smart Snacks rule was part of the implementation of the 2010 Act, which allows the USDA to regulate foods and beverages sold in schools outside of the school meal programs. For example, energy drinks are not permitted in schools, and a study using data from the School Nutrition and Meal Cost Study reported 84% compliance in middle schools in the United States [\n",
|
1035 |
+
"The National School Lunch Program (NSLP) and the School Breakfast Program have to follow specific nutrition requirements consistent with the Dietary Guidelines for Americans. Eating school breakfast and school lunch every day was associated with modestly healthier dietary intakes in US schoolchildren [\n",
|
1036 |
+
"Taken together, the studies presented in this Special Issue highlight the relevance of the role of schools in children’s nutrition. The school environment represents a unique opportunity to positively impact the nutrition of children considering they can consume a significant percentage of their daily intake there. Similarly, a recent systematic review concluded that FV interventions provide a promising avenue by which children’s consumption can be improved. Future interventions should place more focus on vegetable intake [\n"
|
1037 |
+
]
|
1038 |
+
}
|
1039 |
+
],
|
1040 |
+
"source": [
|
1041 |
+
"print(content)"
|
1042 |
+
]
|
1043 |
+
},
|
1044 |
+
{
|
1045 |
+
"cell_type": "code",
|
1046 |
+
"execution_count": null,
|
1047 |
+
"metadata": {},
|
1048 |
+
"outputs": [],
|
1049 |
+
"source": []
|
1050 |
+
}
|
1051 |
+
],
|
1052 |
+
"metadata": {
|
1053 |
+
"kernelspec": {
|
1054 |
+
"display_name": "pinecone-env",
|
1055 |
+
"language": "python",
|
1056 |
+
"name": "python3"
|
1057 |
+
},
|
1058 |
+
"language_info": {
|
1059 |
+
"codemirror_mode": {
|
1060 |
+
"name": "ipython",
|
1061 |
+
"version": 3
|
1062 |
+
},
|
1063 |
+
"file_extension": ".py",
|
1064 |
+
"mimetype": "text/x-python",
|
1065 |
+
"name": "python",
|
1066 |
+
"nbconvert_exporter": "python",
|
1067 |
+
"pygments_lexer": "ipython3",
|
1068 |
+
"version": "3.11.10"
|
1069 |
+
}
|
1070 |
+
},
|
1071 |
+
"nbformat": 4,
|
1072 |
+
"nbformat_minor": 2
|
1073 |
+
}
|
test_download_data.sh
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/bash
|
2 |
+
|
3 |
+
# Specify the number of articles to download
|
4 |
+
limit=10
|
5 |
+
|
6 |
+
# Fetch the list of articles with metadata in XML format
|
7 |
+
response=$(curl -s "https://www.ncbi.nlm.nih.gov/pmc/utils/oa/oa.fcgi?format=pdf&limit=$limit")
|
8 |
+
|
9 |
+
# Parse each record in the response
|
10 |
+
echo "$response" | while read -r line; do
|
11 |
+
# Extract the PMC ID
|
12 |
+
if [[ $line =~ id=\"(PMC[0-9]+)\" ]]; then
|
13 |
+
pmc_id="${BASH_REMATCH[1]}"
|
14 |
+
echo "Processing article ID: $pmc_id"
|
15 |
+
|
16 |
+
# Extract the title for metadata
|
17 |
+
title=$(echo "$response" | sed -n "/<record id=\"$pmc_id\"/,/<\/record>/p" | sed -n 's/.*citation="\(.*\)".*/\1/p')
|
18 |
+
|
19 |
+
# Extract the PDF link for download
|
20 |
+
pdf_link=$(echo "$response" | sed -n "/<record id=\"$pmc_id\"/,/<\/record>/p" | sed -n 's/.*<link format="pdf"[^>]* href="\([^"]*\)".*/\1/p')
|
21 |
+
|
22 |
+
# Check if we found a PDF link
|
23 |
+
if [[ -n $pdf_link ]]; then
|
24 |
+
# Print metadata
|
25 |
+
echo "Title: $title"
|
26 |
+
echo "Downloading PDF from: $pdf_link"
|
27 |
+
|
28 |
+
# Download the PDF
|
29 |
+
curl -O "$pdf_link"
|
30 |
+
|
31 |
+
# Optional: Save metadata to a file
|
32 |
+
echo "Title: $title" >> metadata.txt
|
33 |
+
echo "PDF Link: $pdf_link" >> metadata.txt
|
34 |
+
echo "---------------------" >> metadata.txt
|
35 |
+
else
|
36 |
+
echo "No PDF link found for article ID: $pmc_id"
|
37 |
+
fi
|
38 |
+
fi
|
39 |
+
done
|
tests/__init__.py
ADDED
File without changes
|
tests/test_pinecone.py
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pytest
|
2 |
+
import os
|
3 |
+
import time
|
4 |
+
from openai import OpenAI
|
5 |
+
from pinecone import Pinecone, ServerlessSpec
|
6 |
+
from datasets import load_dataset
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
|
9 |
+
class TestPineconeIntegration:
|
10 |
+
@pytest.fixture(autouse=True)
|
11 |
+
def setup(self):
|
12 |
+
"""Setup test environment and resources"""
|
13 |
+
# Load environment variables
|
14 |
+
load_dotenv("../")
|
15 |
+
|
16 |
+
# Initialize clients
|
17 |
+
self.pc = Pinecone(api_key=os.getenv("PINECONE_API_KEY"))
|
18 |
+
self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
19 |
+
|
20 |
+
# Constants
|
21 |
+
self.MODEL = "text-embedding-3-small"
|
22 |
+
self.index_name = "test-semantic-search-openai"
|
23 |
+
|
24 |
+
yield # This is where the test runs
|
25 |
+
|
26 |
+
# Cleanup after tests
|
27 |
+
try:
|
28 |
+
if self.index_name in self.pc.list_indexes().names():
|
29 |
+
self.pc.delete_index(self.index_name)
|
30 |
+
except Exception as e:
|
31 |
+
print(f"Cleanup failed: {str(e)}")
|
32 |
+
|
33 |
+
def test_01_create_embeddings(self):
|
34 |
+
"""Test OpenAI embedding creation"""
|
35 |
+
sample_texts = [
|
36 |
+
"Sample document text goes here",
|
37 |
+
"there will be several phrases in each batch"
|
38 |
+
]
|
39 |
+
|
40 |
+
res = self.client.embeddings.create(
|
41 |
+
input=sample_texts,
|
42 |
+
model=self.MODEL
|
43 |
+
)
|
44 |
+
|
45 |
+
embeds = [record.embedding for record in res.data]
|
46 |
+
assert len(embeds) == 2
|
47 |
+
assert len(embeds[0]) > 0 # Check if embeddings are non-empty
|
48 |
+
|
49 |
+
return embeds[0] # Return for use in other tests
|
50 |
+
|
51 |
+
def test_02_create_index(self):
|
52 |
+
"""Test Pinecone index creation"""
|
53 |
+
# Get sample embedding dimension from previous test
|
54 |
+
sample_embed = self.test_01_create_embeddings()
|
55 |
+
embedding_dimension = len(sample_embed)
|
56 |
+
|
57 |
+
spec = ServerlessSpec(cloud="aws", region="us-east-1")
|
58 |
+
|
59 |
+
# Create index if it doesn't exist
|
60 |
+
if self.index_name not in self.pc.list_indexes().names():
|
61 |
+
self.pc.create_index(
|
62 |
+
self.index_name,
|
63 |
+
dimension=embedding_dimension,
|
64 |
+
metric='dotproduct',
|
65 |
+
spec=spec
|
66 |
+
)
|
67 |
+
|
68 |
+
# Wait for index to be ready
|
69 |
+
max_retries = 60 # Maximum number of seconds to wait
|
70 |
+
retries = 0
|
71 |
+
while not self.pc.describe_index(self.index_name).status['ready']:
|
72 |
+
if retries >= max_retries:
|
73 |
+
raise TimeoutError("Index creation timed out")
|
74 |
+
time.sleep(1)
|
75 |
+
retries += 1
|
76 |
+
|
77 |
+
# Verify index exists and is ready
|
78 |
+
assert self.index_name in self.pc.list_indexes().names()
|
79 |
+
assert self.pc.describe_index(self.index_name).status['ready']
|
80 |
+
|
81 |
+
def test_03_upload_data(self):
|
82 |
+
"""Test data upload to Pinecone"""
|
83 |
+
# Ensure index exists first
|
84 |
+
self.test_02_create_index()
|
85 |
+
|
86 |
+
# Connect to index
|
87 |
+
index = self.pc.Index(self.index_name)
|
88 |
+
# Load test dataset - using 'trec' instead of 'train'
|
89 |
+
trec = load_dataset('trec', split='train[:10]') # Using smaller dataset for testing
|
90 |
+
|
91 |
+
batch_size = 5
|
92 |
+
total_processed = 0
|
93 |
+
|
94 |
+
for i in range(0, len(trec['text']), batch_size):
|
95 |
+
i_end = min(i + batch_size, len(trec['text']))
|
96 |
+
lines_batch = trec['text'][i:i_end]
|
97 |
+
ids_batch = [str(n) for n in range(i, i_end)]
|
98 |
+
|
99 |
+
# Create embeddings
|
100 |
+
res = self.client.embeddings.create(input=lines_batch, model=self.MODEL)
|
101 |
+
embeds = [record.embedding for record in res.data]
|
102 |
+
|
103 |
+
# Prepare metadata and upsert batch
|
104 |
+
meta = [{'text': line} for line in lines_batch]
|
105 |
+
to_upsert = zip(ids_batch, embeds, meta)
|
106 |
+
|
107 |
+
# Upsert to Pinecone
|
108 |
+
index.upsert(vectors=list(to_upsert))
|
109 |
+
total_processed += len(lines_batch)
|
110 |
+
|
111 |
+
# Wait for a moment to ensure data is indexed
|
112 |
+
time.sleep(5)
|
113 |
+
|
114 |
+
# Verify data was uploaded
|
115 |
+
stats = index.describe_index_stats()
|
116 |
+
print(f'stats: {stats}')
|
117 |
+
# assert stats.total_vector_count == total_processed
|
118 |
+
|
119 |
+
def test_04_query_index(self):
|
120 |
+
"""Test querying the Pinecone index"""
|
121 |
+
# Ensure data is uploaded first
|
122 |
+
self.test_03_upload_data()
|
123 |
+
|
124 |
+
index = self.pc.Index(self.index_name)
|
125 |
+
|
126 |
+
# Create query embedding
|
127 |
+
query = "What caused the Great Depression?"
|
128 |
+
xq = self.client.embeddings.create(input=query, model=self.MODEL).data[0].embedding
|
129 |
+
|
130 |
+
# Query index
|
131 |
+
res = index.query(vector=xq, top_k=5, include_metadata=True)
|
132 |
+
|
133 |
+
# Verify response format
|
134 |
+
assert 'matches' in res
|
135 |
+
assert len(res['matches']) <= 5 # Should return up to 5 results
|
136 |
+
|
137 |
+
# Verify match format
|
138 |
+
for match in res['matches']:
|
139 |
+
assert 'score' in match
|
140 |
+
assert 'metadata' in match
|
141 |
+
assert 'text' in match['metadata']
|
142 |
+
|
143 |
+
def test_05_delete_index(self):
|
144 |
+
"""Test index deletion"""
|
145 |
+
# Ensure index exists first
|
146 |
+
self.test_02_create_index()
|
147 |
+
|
148 |
+
# Delete index
|
149 |
+
self.pc.delete_index(self.index_name)
|
150 |
+
|
151 |
+
# Verify deletion
|
152 |
+
assert self.index_name not in self.pc.list_indexes().names()
|
tests/test_pinecone_embeddings.ipynb
ADDED
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 1,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [
|
8 |
+
{
|
9 |
+
"name": "stderr",
|
10 |
+
"output_type": "stream",
|
11 |
+
"text": [
|
12 |
+
"/Users/larawehbe/Documents/fakkerai/sehatech/venv/lib/python3.13/site-packages/pinecone/data/index.py:1: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
13 |
+
" from tqdm.autonotebook import tqdm\n"
|
14 |
+
]
|
15 |
+
},
|
16 |
+
{
|
17 |
+
"data": {
|
18 |
+
"text/plain": [
|
19 |
+
"True"
|
20 |
+
]
|
21 |
+
},
|
22 |
+
"execution_count": 1,
|
23 |
+
"metadata": {},
|
24 |
+
"output_type": "execute_result"
|
25 |
+
}
|
26 |
+
],
|
27 |
+
"source": [
|
28 |
+
"import os\n",
|
29 |
+
"import pinecone\n",
|
30 |
+
"from langchain.document_loaders import PyPDFLoader\n",
|
31 |
+
"from langchain.embeddings import OpenAIEmbeddings # Adjust to your embedding model\n",
|
32 |
+
"from langchain.vectorstores import Pinecone\n",
|
33 |
+
"from langchain.chains import RetrievalQA\n",
|
34 |
+
"from langchain.llms import OpenAI # Replace with the LLM of your choice\n",
|
35 |
+
"from dotenv import load_dotenv\n",
|
36 |
+
"load_dotenv()\n",
|
37 |
+
"\n"
|
38 |
+
]
|
39 |
+
},
|
40 |
+
{
|
41 |
+
"cell_type": "code",
|
42 |
+
"execution_count": 4,
|
43 |
+
"metadata": {},
|
44 |
+
"outputs": [],
|
45 |
+
"source": [
|
46 |
+
"# Initialize Pinecone\n",
|
47 |
+
"\n",
|
48 |
+
"pc = pinecone.Pinecone(api_key=os.getenv(\"PINECONE_API_KEY\"))\n",
|
49 |
+
"index_name = \"clec16a-study\"\n"
|
50 |
+
]
|
51 |
+
},
|
52 |
+
{
|
53 |
+
"cell_type": "code",
|
54 |
+
"execution_count": 7,
|
55 |
+
"metadata": {},
|
56 |
+
"outputs": [],
|
57 |
+
"source": [
|
58 |
+
"spec = pinecone.ServerlessSpec(cloud='aws',region=\"us-east-1\")\n",
|
59 |
+
"\n",
|
60 |
+
"# Create the index if it doesn't exist\n",
|
61 |
+
"if index_name not in pc.list_indexes():\n",
|
62 |
+
" pc.create_index(index_name, dimension=1536, spec=spec) # Adjust dimension as needed\n",
|
63 |
+
"# Connect to the index\n",
|
64 |
+
"index = pc.Index(index_name)"
|
65 |
+
]
|
66 |
+
},
|
67 |
+
{
|
68 |
+
"cell_type": "code",
|
69 |
+
"execution_count": 8,
|
70 |
+
"metadata": {},
|
71 |
+
"outputs": [],
|
72 |
+
"source": [
|
73 |
+
"import openai \n",
|
74 |
+
"openai.api_key = os.getenv(\"OPENAI_API_KEY\")"
|
75 |
+
]
|
76 |
+
},
|
77 |
+
{
|
78 |
+
"cell_type": "code",
|
79 |
+
"execution_count": 9,
|
80 |
+
"metadata": {},
|
81 |
+
"outputs": [
|
82 |
+
{
|
83 |
+
"name": "stderr",
|
84 |
+
"output_type": "stream",
|
85 |
+
"text": [
|
86 |
+
"/var/folders/qt/8nj7tb591mx9xtqkgz7mjyjh0000gn/T/ipykernel_17808/4087293823.py:7: LangChainDeprecationWarning: The class `OpenAIEmbeddings` was deprecated in LangChain 0.0.9 and will be removed in 1.0. An updated version of the class exists in the :class:`~langchain-openai package and should be used instead. To use it run `pip install -U :class:`~langchain-openai` and import as `from :class:`~langchain_openai import OpenAIEmbeddings``.\n",
|
87 |
+
" embedding_model = OpenAIEmbeddings(model=MODEL)\n"
|
88 |
+
]
|
89 |
+
}
|
90 |
+
],
|
91 |
+
"source": [
|
92 |
+
"MODEL = 'text-embedding-ada-002' \n",
|
93 |
+
"pdf_path = \"../data/main.pdf\" # Replace with your actual PDF path\n",
|
94 |
+
"loader = PyPDFLoader(pdf_path)\n",
|
95 |
+
"documents = loader.load()\n",
|
96 |
+
"\n",
|
97 |
+
"# Initialize embedding model\n",
|
98 |
+
"embedding_model = OpenAIEmbeddings(model=MODEL)"
|
99 |
+
]
|
100 |
+
},
|
101 |
+
{
|
102 |
+
"cell_type": "code",
|
103 |
+
"execution_count": 12,
|
104 |
+
"metadata": {},
|
105 |
+
"outputs": [],
|
106 |
+
"source": [
|
107 |
+
"# Define function to create or connect to an existing index\n",
|
108 |
+
"def create_or_connect_index(index_name, dimension):\n",
|
109 |
+
" spec = pinecone.ServerlessSpec(cloud='aws',region=\"us-east-1\")\n",
|
110 |
+
" if index_name not in pc.list_indexes().names():\n",
|
111 |
+
" pc.create_index(\n",
|
112 |
+
" name=index_name,\n",
|
113 |
+
" dimension=dimension,\n",
|
114 |
+
" metric='cosine', # You can use 'dotproduct' or other metrics if needed\n",
|
115 |
+
" spec=spec\n",
|
116 |
+
" )\n",
|
117 |
+
" return pc.Index(index_name)"
|
118 |
+
]
|
119 |
+
},
|
120 |
+
{
|
121 |
+
"cell_type": "code",
|
122 |
+
"execution_count": null,
|
123 |
+
"metadata": {},
|
124 |
+
"outputs": [
|
125 |
+
{
|
126 |
+
"name": "stdout",
|
127 |
+
"output_type": "stream",
|
128 |
+
"text": [
|
129 |
+
"sampleembedding: 1536\n"
|
130 |
+
]
|
131 |
+
}
|
132 |
+
],
|
133 |
+
"source": [
|
134 |
+
"sample_embedding = embedding_model.embed_query(\"Test\")\n",
|
135 |
+
"index = create_or_connect_index(index_name, dimension=len(sample_embedding))\n",
|
136 |
+
"print(f'sample embedding: {len(sample_embedding)}')"
|
137 |
+
]
|
138 |
+
},
|
139 |
+
{
|
140 |
+
"cell_type": "code",
|
141 |
+
"execution_count": 23,
|
142 |
+
"metadata": {},
|
143 |
+
"outputs": [],
|
144 |
+
"source": [
|
145 |
+
"for i, doc in enumerate(documents):\n",
|
146 |
+
" embedding = embedding_model.embed_query(doc.page_content)\n",
|
147 |
+
" pinecone_id = f\"page-{i}\"\n",
|
148 |
+
" metadata = {\"text\": doc.page_content} # Include a 'text' snippet in metadata\n",
|
149 |
+
" index.upsert([(pinecone_id, embedding, metadata)]) # Upsert embedding with metadata"
|
150 |
+
]
|
151 |
+
},
|
152 |
+
{
|
153 |
+
"cell_type": "code",
|
154 |
+
"execution_count": 24,
|
155 |
+
"metadata": {},
|
156 |
+
"outputs": [],
|
157 |
+
"source": [
|
158 |
+
"from langchain_openai import ChatOpenAI\n",
|
159 |
+
"\n",
|
160 |
+
"\n",
|
161 |
+
"vector_store = Pinecone.from_existing_index(index_name=index_name, embedding=embedding_model)\n",
|
162 |
+
"\n",
|
163 |
+
"# Set up RetrievalQA chain for querying using a chat-based model for better responses\n",
|
164 |
+
"llm = ChatOpenAI(model=\"gpt-4\", openai_api_key=openai.api_key) # Replace with the chat model of choice\n",
|
165 |
+
"qa_chain = RetrievalQA.from_chain_type(llm=llm, retriever=vector_store.as_retriever())"
|
166 |
+
]
|
167 |
+
},
|
168 |
+
{
|
169 |
+
"cell_type": "code",
|
170 |
+
"execution_count": 25,
|
171 |
+
"metadata": {},
|
172 |
+
"outputs": [
|
173 |
+
{
|
174 |
+
"name": "stdout",
|
175 |
+
"output_type": "stream",
|
176 |
+
"text": [
|
177 |
+
"Question: What role does CLEC16A play in mitochondrial quality control, and why is this important for cellular health?\n",
|
178 |
+
"Answer: CLEC16A is an E3 ubiquitin ligase that plays a significant role in mitochondrial quality control through a process called mitophagy. Mitophagy is a type of autophagy where damaged mitochondria are selectively eliminated from the cell. CLEC16A regulates mitophagy by forming a tripartite complex with another E3 ubiquitin ligase, RNF41, and a ubiquitin-specific peptidase, USP8. This complex controls the activity of the mitophagy regulator PRKN/Parkin. \n",
|
179 |
+
"\n",
|
180 |
+
"Maintaining mitochondrial quality control is crucial for cellular health as damaged mitochondria can lead to a decrease in energy production, increase in harmful reactive oxygen species, and potential induction of cell death. Therefore, the role of CLEC16A in mitochondrial quality control is important for maintaining cellular health and function. It is also noteworthy that the gene for CLEC16A is associated with over 20 human diseases, including diabetes, cardiovascular disease, stroke, multiple sclerosis, arthritis, and Crohn's disease, further underscoring its importance in cellular health.\n",
|
181 |
+
"\n",
|
182 |
+
"Question: How does the intrinsically disordered protein region (IDPR) within CLEC16A impact its stability and interaction with RNF41?\n",
|
183 |
+
"Answer: The intrinsically disordered protein region (IDPR) within CLEC16A plays a significant role in its stability and its interaction with RNF41. The IDPR facilitates CLEC16A's turnover and degradation, with mutations in this region leading to increased stability of the protein. This region also contributes to the interaction between CLEC16A and RNF41, a process that is essential for the assembly of the CLEC16A-RNF41-USP8 mitophagy complex. \n",
|
184 |
+
"\n",
|
185 |
+
"Furthermore, the IDPR within CLEC16A is required for RNF41-mediated turnover of CLEC16A, as the removal or shuffling of the IDPR prevents RNF41 from reducing CLEC16A protein levels. This suggests the internal IDPR destabilizes CLEC16A and that this action depends upon the IDPR's amino acid sequence order.\n",
|
186 |
+
"\n",
|
187 |
+
"Moreover, the lysine residues within the IDPR are crucial for both CLEC16A turnover and for RNF41 to act upon CLEC16A. However, simply retaining the lysine residues in their original positions within a shuffled IDPR does not restore CLEC16A turnover or RNF41 action, indicating that the entire IDPR sequence needs to be intact for RNF41 to destabilize CLEC16A. \n",
|
188 |
+
"\n",
|
189 |
+
"Overall, the internal IDPR within CLEC16A plays a key role in the protein's stability, its interaction with RNF41, and its regulation within the cellular environment.\n",
|
190 |
+
"\n",
|
191 |
+
"Question: What is the significance of the CLEC16A-RNF41 complex in the regulation of mitophagy?\n",
|
192 |
+
"Answer: The CLEC16A-RNF41 complex plays a crucial role in the regulation of mitophagy, a process for eliminating damaged mitochondria. The CLEC16A gene encodes an E3 ubiquitin ligase that helps maintain mitochondrial health through selective mitochondrial autophagy (mitophagy). CLEC16A forms a complex with another E3 ligase, RNF41, and a ubiquitin-specific peptidase, USP8, to control the activity of the mitophagy regulator PRKN/Parkin. CLEC16A directly binds and ubiquitinates RNF41 to promote assembly and stability of the tripartite mitophagy complex. The study found that an intrinsically disordered protein region (IDPR) within CLEC16A is crucial for its function and turnover. The IDPR is essential to control the reciprocal regulatory balance between CLEC16A and RNF41, a balance which could possibly be targeted to improve mitochondrial health in disease.\n",
|
193 |
+
"\n"
|
194 |
+
]
|
195 |
+
}
|
196 |
+
],
|
197 |
+
"source": [
|
198 |
+
"# Define the list of questions\n",
|
199 |
+
"questions = [\n",
|
200 |
+
" \"What role does CLEC16A play in mitochondrial quality control, and why is this important for cellular health?\",\n",
|
201 |
+
" \"How does the intrinsically disordered protein region (IDPR) within CLEC16A impact its stability and interaction with RNF41?\",\n",
|
202 |
+
" \"What is the significance of the CLEC16A-RNF41 complex in the regulation of mitophagy?\",\n",
|
203 |
+
" # Add more questions as needed\n",
|
204 |
+
"]\n",
|
205 |
+
"\n",
|
206 |
+
"# Query each question and print the answers\n",
|
207 |
+
"for question in questions:\n",
|
208 |
+
" answer = qa_chain.run(question)\n",
|
209 |
+
" print(f\"Question: {question}\")\n",
|
210 |
+
" print(f\"Answer: {answer}\\n\")\n"
|
211 |
+
]
|
212 |
+
},
|
213 |
+
{
|
214 |
+
"cell_type": "markdown",
|
215 |
+
"metadata": {},
|
216 |
+
"source": [
|
217 |
+
"## Now, turn it into a chat"
|
218 |
+
]
|
219 |
+
},
|
220 |
+
{
|
221 |
+
"cell_type": "code",
|
222 |
+
"execution_count": 30,
|
223 |
+
"metadata": {},
|
224 |
+
"outputs": [],
|
225 |
+
"source": [
|
226 |
+
"chat_llm = ChatOpenAI(model=\"gpt-4o\", openai_api_key=openai.api_key)\n",
|
227 |
+
"chat_qa_chain = RetrievalQA.from_chain_type(llm=llm, retriever=vector_store.as_retriever(search_kwargs={\"k\" : 3}))\n"
|
228 |
+
]
|
229 |
+
},
|
230 |
+
{
|
231 |
+
"cell_type": "code",
|
232 |
+
"execution_count": 32,
|
233 |
+
"metadata": {},
|
234 |
+
"outputs": [
|
235 |
+
{
|
236 |
+
"name": "stdout",
|
237 |
+
"output_type": "stream",
|
238 |
+
"text": [
|
239 |
+
"Welcome to the CLEC16A Chat System! Ask any question, or type 'exit' to quit.\n",
|
240 |
+
"AI: The document is a scientific article discussing research on the regulation of CLEC16A stability through an intrinsically disordered protein region (IDPR) and its implications in various diseases. It includes detailed methodologies such as statistical analysis using Prism software, data availability, acknowledgments, and contributions from various authors supported by institutions like the University of Michigan and the NIH. The research explores genetic associations with diseases like type 1 diabetes, multiple sclerosis, and myocardial infarction. The document also discusses experimental procedures including protein purification, nuclear magnetic resonance (NMR), circular dichroism, and cell culture techniques. Additionally, it highlights the significance of intrinsically disordered proteins in cellular functions and diseases. The article contains references to previous studies and provides a comprehensive overview of the research conducted on CLEC16A and its regulatory mechanisms.\n",
|
241 |
+
"\n",
|
242 |
+
"AI: 1. **Research and Data Availability**: The study involves biophysical studies of recombinant proteins, and all data are contained within the manuscript. Supporting information is available in the referenced article.\n",
|
243 |
+
"\n",
|
244 |
+
"2. **Funding and Support**: The research received significant support from institutions such as the University of Michigan Center for Structural Biology and various grants from organizations including the NIH, JDRF, and the Department of Veterans Affairs. Specific grants and awards are mentioned, highlighting the financial backing that facilitated the study.\n",
|
245 |
+
"\n",
|
246 |
+
"3. **Contributions and Acknowledgments**: The research involved multiple contributors, with specific roles such as conceptualization, investigation, formal analysis, and writing. Acknowledgments are given to individuals and facilities that provided assistance, such as the University of Michigan BioNMR Core for help with NMR studies. The authors declare no conflicts of interest.\n",
|
247 |
+
"\n",
|
248 |
+
"AI: The document you provided seems to be a scientific research article about the protein CLEC16A and its intrinsically disordered protein region (IDPR), as well as its regulation and structural characteristics. As an internal medicine doctor, the direct application of this specific research to your practice may not be immediately clear unless the findings relate to a particular medical condition or treatment relevant to your patients.\n",
|
249 |
+
"\n",
|
250 |
+
"However, staying informed about the latest scientific research can be beneficial in several ways:\n",
|
251 |
+
"\n",
|
252 |
+
"1. **Understanding Disease Mechanisms**: Research into proteins like CLEC16A can provide insights into the mechanisms of diseases, especially if these proteins are implicated in conditions that you treat.\n",
|
253 |
+
"\n",
|
254 |
+
"2. **Potential for New Treatments**: Understanding the regulation and stability of proteins might lead to the development of new therapeutic targets or drugs in the future.\n",
|
255 |
+
"\n",
|
256 |
+
"3. **Educating Patients**: Knowledge of ongoing research allows you to provide patients with the most current information about their conditions and potential future therapies.\n",
|
257 |
+
"\n",
|
258 |
+
"4. **Interdisciplinary Collaboration**: Familiarity with cutting-edge research can facilitate collaboration with specialists, researchers, or clinical trials that might benefit your patients.\n",
|
259 |
+
"\n",
|
260 |
+
"5. **Continuing Education**: Engaging with scientific literature is a part of lifelong learning and can help you stay current with medical advancements and innovations.\n",
|
261 |
+
"\n",
|
262 |
+
"If the study is related to a specific condition that you encounter, it would be worthwhile to explore how these findings might translate into clinical practices over time.\n",
|
263 |
+
"\n",
|
264 |
+
"AI: I'm sorry, but I don't have enough information to answer your question. Could you please provide more context or clarify your inquiry?\n",
|
265 |
+
"\n",
|
266 |
+
"AI: The document appears to be a scientific article related to biochemistry and molecular biology, specifically focusing on protein interactions and intrinsically disordered protein regions (IDPR) in the context of diseases like diabetes and autoimmune disorders. As an AI engineer, you might not directly benefit from the specific scientific content unless your work involves bioinformatics, computational biology, or the development of AI models for analyzing biological data. If your work involves these areas, you could gain insights into the types of data and analyses that are relevant in this field, which could inform the development of AI tools or models. Otherwise, the document may not be directly relevant to your work as an AI engineer.\n",
|
267 |
+
"\n",
|
268 |
+
"AI: Goodbye! If you have any more questions in the future, feel free to ask. Have a great day!\n",
|
269 |
+
"\n",
|
270 |
+
"Exiting the chat. Goodbye!\n"
|
271 |
+
]
|
272 |
+
}
|
273 |
+
],
|
274 |
+
"source": [
|
275 |
+
"def chat_system():\n",
|
276 |
+
" print(\"Welcome to the CLEC16A Chat System! Ask any question, or type 'exit' to quit.\")\n",
|
277 |
+
" while True:\n",
|
278 |
+
" question = input(\"You: \")\n",
|
279 |
+
" if question.lower() in ['exit', 'quit']:\n",
|
280 |
+
" print(\"Exiting the chat. Goodbye!\")\n",
|
281 |
+
" break\n",
|
282 |
+
" answer = chat_qa_chain.run(question)\n",
|
283 |
+
" print(f\"AI: {answer}\\n\")\n",
|
284 |
+
"\n",
|
285 |
+
"# Run the chat system\n",
|
286 |
+
"chat_system()\n"
|
287 |
+
]
|
288 |
+
},
|
289 |
+
{
|
290 |
+
"cell_type": "markdown",
|
291 |
+
"metadata": {},
|
292 |
+
"source": [
|
293 |
+
"### Now, i want to add a prompt template"
|
294 |
+
]
|
295 |
+
},
|
296 |
+
{
|
297 |
+
"cell_type": "code",
|
298 |
+
"execution_count": 33,
|
299 |
+
"metadata": {},
|
300 |
+
"outputs": [
|
301 |
+
{
|
302 |
+
"name": "stdout",
|
303 |
+
"output_type": "stream",
|
304 |
+
"text": [
|
305 |
+
"Welcome to the CLEC16A Chat System! Type 'exit' to quit.\n",
|
306 |
+
"AI (Prompt): Sure, I can help with that. Please go ahead with your questions.\n",
|
307 |
+
"\n",
|
308 |
+
"AI: The article investigates the role of an internal intrinsically disordered protein region (IDPR) within the CLEC16A protein, which is an E3 ubiquitin ligase involved in mitochondrial quality control through mitophagy. CLEC16A forms a complex with other proteins, RNF41 and USP8, to regulate mitochondrial health. The study highlights that the internal IDPR of CLEC16A is crucial for the protein's function and turnover. It is essential for the binding and ubiquitination of RNF41, which promotes the stability and assembly of the CLEC16A–RNF41–USP8 complex. Disruption of this IDPR prevents CLEC16A turnover and destabilizes the mitophagy complex. The presence of the IDPR in CLEC16A was confirmed using NMR and CD spectroscopy. This research suggests that targeting the IDPR could improve mitochondrial health in diseases associated with CLEC16A, such as diabetes, cardiovascular disease, and multiple sclerosis.\n",
|
309 |
+
"\n",
|
310 |
+
"AI: Based on the document's content, here are three benefits related to mitochondrial quality control facilitated by CLEC16A:\n",
|
311 |
+
"\n",
|
312 |
+
"1. **Mitophagy and Mitochondrial Health**: CLEC16A, as an E3 ubiquitin ligase, regulates mitochondrial quality control through the process of mitophagy, which eliminates damaged mitochondria. This helps maintain mitochondrial health.\n",
|
313 |
+
"\n",
|
314 |
+
"2. **Tripartite Complex Formation**: CLEC16A forms a complex with RNF41 and USP8, which together regulate the activity of the mitophagy regulator PRKN/Parkin. This complex plays a crucial role in maintaining mitochondrial function.\n",
|
315 |
+
"\n",
|
316 |
+
"3. **Disease Prevention and Cellular Function**: Proper functioning of CLEC16A in mitochondrial quality control is crucial for preventing cellular dysfunction and diseases such as diabetes, cardiovascular disease, and multiple sclerosis, as it is associated with over 20 human diseases. This highlights the importance of maintaining mitochondrial integrity and function in various cell types, including pancreatic β-cells, sensory neurons, and immune cells.\n",
|
317 |
+
"\n",
|
318 |
+
"AI: I'm sorry, but I don't have access to real-time information, including current weather conditions. I recommend checking a weather app or website for the most up-to-date information.\n",
|
319 |
+
"\n",
|
320 |
+
"AI: Goodbye! If you have any more questions in the future, feel free to ask.\n",
|
321 |
+
"\n",
|
322 |
+
"Exiting the chat. Goodbye!\n"
|
323 |
+
]
|
324 |
+
}
|
325 |
+
],
|
326 |
+
"source": [
|
327 |
+
"# Define the initial system prompt\n",
|
328 |
+
"initial_prompt = (\n",
|
329 |
+
" \"You are an AI assistant specializing in CLEC16A-related research, focusing on mitochondrial quality control, \"\n",
|
330 |
+
" \"the role of intrinsically disordered protein regions, and disease implications. \"\n",
|
331 |
+
" \"Answer the following questions based on the document's content.\"\n",
|
332 |
+
")\n",
|
333 |
+
"\n",
|
334 |
+
"# Define the chat function with prompt\n",
|
335 |
+
"def chat_system():\n",
|
336 |
+
" print(\"Welcome to the CLEC16A Chat System! Type 'exit' to quit.\")\n",
|
337 |
+
" \n",
|
338 |
+
" # Send the initial prompt\n",
|
339 |
+
" response = chat_qa_chain.run(initial_prompt)\n",
|
340 |
+
" print(f\"AI (Prompt): {response}\\n\")\n",
|
341 |
+
"\n",
|
342 |
+
" # Start the chat loop\n",
|
343 |
+
" while True:\n",
|
344 |
+
" question = input(\"You: \")\n",
|
345 |
+
" if question.lower() in ['exit', 'quit']:\n",
|
346 |
+
" print(\"Exiting the chat. Goodbye!\")\n",
|
347 |
+
" break\n",
|
348 |
+
" # Prepend initial prompt to each question\n",
|
349 |
+
" full_prompt = f\"{initial_prompt}\\n\\n{question}\"\n",
|
350 |
+
" answer = chat_qa_chain.run(full_prompt)\n",
|
351 |
+
" print(f\"AI: {answer}\\n\")\n",
|
352 |
+
"\n",
|
353 |
+
"# Run the chat system\n",
|
354 |
+
"chat_system()\n"
|
355 |
+
]
|
356 |
+
},
|
357 |
+
{
|
358 |
+
"cell_type": "code",
|
359 |
+
"execution_count": null,
|
360 |
+
"metadata": {},
|
361 |
+
"outputs": [],
|
362 |
+
"source": []
|
363 |
+
}
|
364 |
+
],
|
365 |
+
"metadata": {
|
366 |
+
"kernelspec": {
|
367 |
+
"display_name": "venv",
|
368 |
+
"language": "python",
|
369 |
+
"name": "python3"
|
370 |
+
},
|
371 |
+
"language_info": {
|
372 |
+
"codemirror_mode": {
|
373 |
+
"name": "ipython",
|
374 |
+
"version": 3
|
375 |
+
},
|
376 |
+
"file_extension": ".py",
|
377 |
+
"mimetype": "text/x-python",
|
378 |
+
"name": "python",
|
379 |
+
"nbconvert_exporter": "python",
|
380 |
+
"pygments_lexer": "ipython3",
|
381 |
+
"version": "3.13.0"
|
382 |
+
}
|
383 |
+
},
|
384 |
+
"nbformat": 4,
|
385 |
+
"nbformat_minor": 2
|
386 |
+
}
|
tests/test_pinecone_rag.py
ADDED
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import sys
|
2 |
+
import os
|
3 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
4 |
+
import unittest
|
5 |
+
from unittest.mock import MagicMock, patch, mock_open
|
6 |
+
import pinecone
|
7 |
+
from langchain.schema import Document
|
8 |
+
from core.rag_engine import RAGPrep
|
9 |
+
from typing import List, Dict, Optional
|
10 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
11 |
+
from langchain_community.document_loaders import PyPDFLoader, DirectoryLoader
|
12 |
+
from langchain_openai import OpenAIEmbeddings
|
13 |
+
import pinecone
|
14 |
+
from tqdm.auto import tqdm
|
15 |
+
from langchain.schema import Document
|
16 |
+
from config import get_settings
|
17 |
+
|
18 |
+
|
19 |
+
class TestRAGPrep(unittest.TestCase):
|
20 |
+
def setUp(self):
|
21 |
+
"""Set up test fixtures"""
|
22 |
+
self.settings = get_settings()
|
23 |
+
self.mock_settings = MagicMock()
|
24 |
+
self.mock_settings.INDEX_NAME = "test-index"
|
25 |
+
self.mock_settings.PINECONE_API_KEY = self.settings.PINECONE_API_KEY
|
26 |
+
self.mock_settings.CLOUD = "aws"
|
27 |
+
self.mock_settings.REGION = "us-east-1"
|
28 |
+
self.mock_settings.PDF_DIRECTORY = self.settings.PDF_DIRECTORY
|
29 |
+
self.mock_settings.CHUNK_SIZE = 1000
|
30 |
+
self.mock_settings.CHUNK_OVERLAP = 200
|
31 |
+
self.mock_settings.DIMENSIONS = 1536
|
32 |
+
self.mock_settings.OPENAI_API_KEY = self.settings.OPENAI_API_KEY
|
33 |
+
|
34 |
+
# Create patcher for get_settings and other dependencies
|
35 |
+
self.settings_patcher = patch('core.rag_engine.get_settings', return_value=self.mock_settings)
|
36 |
+
self.embeddings_patcher = patch('core.rag_engine.OpenAIEmbeddings')
|
37 |
+
self.pinecone_patcher = patch('core.rag_engine.pinecone.Pinecone')
|
38 |
+
|
39 |
+
# Start all patchers
|
40 |
+
self.mock_get_settings = self.settings_patcher.start()
|
41 |
+
self.mock_embeddings = self.embeddings_patcher.start()
|
42 |
+
self.mock_pinecone = self.pinecone_patcher.start()
|
43 |
+
|
44 |
+
def tearDown(self):
|
45 |
+
"""Clean up after tests"""
|
46 |
+
self.settings_patcher.stop()
|
47 |
+
self.embeddings_patcher.stop()
|
48 |
+
self.pinecone_patcher.stop()
|
49 |
+
|
50 |
+
def test_init(self):
|
51 |
+
"""Test RAGPrep initialization"""
|
52 |
+
# Create instance
|
53 |
+
rag_prep = RAGPrep()
|
54 |
+
|
55 |
+
# Assert initialization
|
56 |
+
self.assertEqual(rag_prep.index_name, "test-index")
|
57 |
+
self.assertEqual(rag_prep.settings, self.mock_settings)
|
58 |
+
self.mock_pinecone.assert_called_once_with(self.mock_settings.PINECONE_API_KEY)
|
59 |
+
self.mock_embeddings.assert_called_once_with(openai_api_key=self.mock_settings.OPENAI_API_KEY)
|
60 |
+
|
61 |
+
@patch('core.rag_engine.DirectoryLoader')
|
62 |
+
def test_load_and_split_pdfs(self, mock_loader_class):
|
63 |
+
"""Test PDF loading and splitting"""
|
64 |
+
# Setup mock documents
|
65 |
+
mock_docs = [
|
66 |
+
Document(page_content="Test content 1", metadata={"source": "test1.pdf", "page": 1}),
|
67 |
+
Document(page_content="Test content 2", metadata={"source": "test2.pdf", "page": 1})
|
68 |
+
]
|
69 |
+
|
70 |
+
# Configure the mock loader
|
71 |
+
mock_loader_instance = MagicMock()
|
72 |
+
mock_loader_instance.load.return_value = mock_docs
|
73 |
+
mock_loader_class.return_value = mock_loader_instance
|
74 |
+
|
75 |
+
# Create instance and test
|
76 |
+
rag_prep = RAGPrep()
|
77 |
+
chunks = rag_prep.load_and_split_pdfs()
|
78 |
+
|
79 |
+
# Assertions
|
80 |
+
self.assertIsInstance(chunks, list)
|
81 |
+
mock_loader_class.assert_called_once_with(
|
82 |
+
self.mock_settings.PDF_DIRECTORY,
|
83 |
+
glob="**/*.pdf",
|
84 |
+
loader_cls=PyPDFLoader
|
85 |
+
)
|
86 |
+
mock_loader_instance.load.assert_called_once()
|
87 |
+
|
88 |
+
def test_process_and_upload(self):
|
89 |
+
"""Test processing and uploading documents"""
|
90 |
+
# Setup mock documents
|
91 |
+
mock_docs = [
|
92 |
+
Document(page_content="Test 1", metadata={"source": "test.pdf", "page": 1}),
|
93 |
+
Document(page_content="Test 2", metadata={"source": "test.pdf", "page": 2})
|
94 |
+
]
|
95 |
+
|
96 |
+
# Create mock embeddings instance
|
97 |
+
mock_embeddings_instance = MagicMock()
|
98 |
+
mock_embeddings_instance.embed_documents.return_value = [[0.1] * 1536, [0.2] * 1536]
|
99 |
+
self.mock_embeddings.return_value = mock_embeddings_instance
|
100 |
+
|
101 |
+
# Mock the index
|
102 |
+
mock_index = MagicMock()
|
103 |
+
self.mock_pinecone.return_value.Index.return_value = mock_index
|
104 |
+
|
105 |
+
# Mock load_and_split_pdfs
|
106 |
+
with patch.object(RAGPrep, 'load_and_split_pdfs', return_value=mock_docs):
|
107 |
+
# Create instance and test
|
108 |
+
rag_prep = RAGPrep()
|
109 |
+
rag_prep.process_and_upload()
|
110 |
+
|
111 |
+
# Assertions
|
112 |
+
mock_embeddings_instance.embed_documents.assert_called_once()
|
113 |
+
self.assertTrue(mock_index.upsert.called)
|
114 |
+
# Verify the format of the upsert call
|
115 |
+
called_args = mock_index.upsert.call_args[1]['vectors']
|
116 |
+
self.assertEqual(len(called_args), 2) # Two documents
|
117 |
+
self.assertTrue(all(len(v[1]) == 1536 for v in called_args))
|
118 |
+
def test_cleanup_index_success(self):
|
119 |
+
"""Test successful index cleanup"""
|
120 |
+
with patch('pinecone.Pinecone') as mock_pinecone:
|
121 |
+
# Setup mock
|
122 |
+
mock_pc = mock_pinecone.return_value
|
123 |
+
mock_pc.list_indexes.return_value.names.return_value = ["test-index"]
|
124 |
+
mock_index = MagicMock()
|
125 |
+
mock_pc.Index.return_value = mock_index
|
126 |
+
|
127 |
+
# Create instance and test
|
128 |
+
rag_prep = RAGPrep()
|
129 |
+
result = rag_prep.cleanup_index()
|
130 |
+
|
131 |
+
# Assertions
|
132 |
+
self.assertTrue(result)
|
133 |
+
mock_index.delete.assert_called_once_with(delete_all=True)
|
134 |
+
|
135 |
+
def test_cleanup_index_no_index(self):
|
136 |
+
"""Test cleanup when index doesn't exist"""
|
137 |
+
with patch('pinecone.Pinecone') as mock_pinecone:
|
138 |
+
# Setup mock
|
139 |
+
mock_pc = mock_pinecone.return_value
|
140 |
+
mock_pc.list_indexes.return_value.names.return_value = []
|
141 |
+
|
142 |
+
# Create instance and test
|
143 |
+
rag_prep = RAGPrep()
|
144 |
+
result = rag_prep.cleanup_index()
|
145 |
+
|
146 |
+
# Assertions
|
147 |
+
self.assertTrue(result)
|
148 |
+
mock_pc.Index.assert_not_called()
|
149 |
+
|
150 |
+
def test_cleanup_index_error(self):
|
151 |
+
"""Test cleanup with error"""
|
152 |
+
with patch('pinecone.Pinecone') as mock_pinecone:
|
153 |
+
# Setup mock to raise exception
|
154 |
+
mock_pc = mock_pinecone.return_value
|
155 |
+
mock_pc.list_indexes.return_value.names.return_value = ["test-index"]
|
156 |
+
mock_pc.Index.side_effect = Exception("Test error")
|
157 |
+
|
158 |
+
# Create instance and test
|
159 |
+
rag_prep = RAGPrep()
|
160 |
+
result = rag_prep.cleanup_index()
|
161 |
+
|
162 |
+
# Assertions
|
163 |
+
self.assertFalse(result)
|
164 |
+
|
165 |
+
if __name__ == '__main__':
|
166 |
+
unittest.main()
|
tests/test_rag_pdf.ipynb
ADDED
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"cells": [
|
3 |
+
{
|
4 |
+
"cell_type": "code",
|
5 |
+
"execution_count": 4,
|
6 |
+
"metadata": {},
|
7 |
+
"outputs": [],
|
8 |
+
"source": [
|
9 |
+
"from langchain_community.document_loaders import PyPDFLoader\n"
|
10 |
+
]
|
11 |
+
},
|
12 |
+
{
|
13 |
+
"cell_type": "code",
|
14 |
+
"execution_count": 5,
|
15 |
+
"metadata": {},
|
16 |
+
"outputs": [],
|
17 |
+
"source": [
|
18 |
+
"import getpass\n",
|
19 |
+
"import os\n",
|
20 |
+
"from dotenv import load_dotenv\n",
|
21 |
+
"\n",
|
22 |
+
"load_dotenv()\n",
|
23 |
+
"os.environ[\"OPENAI_API_KEY\"] = os.getenv(\"OPENAI_API_KEY\")\n",
|
24 |
+
"\n",
|
25 |
+
"from langchain_openai import ChatOpenAI\n",
|
26 |
+
"\n",
|
27 |
+
"llm = ChatOpenAI(model=\"gpt-4o\")"
|
28 |
+
]
|
29 |
+
},
|
30 |
+
{
|
31 |
+
"cell_type": "code",
|
32 |
+
"execution_count": 6,
|
33 |
+
"metadata": {},
|
34 |
+
"outputs": [],
|
35 |
+
"source": [
|
36 |
+
"from langchain_core.vectorstores import InMemoryVectorStore\n",
|
37 |
+
"from langchain_openai import OpenAIEmbeddings\n",
|
38 |
+
"from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
|
39 |
+
"\n",
|
40 |
+
"text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n"
|
41 |
+
]
|
42 |
+
},
|
43 |
+
{
|
44 |
+
"cell_type": "code",
|
45 |
+
"execution_count": null,
|
46 |
+
"metadata": {},
|
47 |
+
"outputs": [
|
48 |
+
{
|
49 |
+
"data": {
|
50 |
+
"text/plain": [
|
51 |
+
"{'input': 'What is this paper about?',\n",
|
52 |
+
" 'context': [Document(id='de54a167-b052-4340-8c9d-c96f3b20b1c8', metadata={'source': '../data/main.pdf', 'page': 12}, page_content='Dunnett’s post hoc multiple comparisons test. A 5% sig-\\nnificance level was used for all statistical tests. All statis-\\ntical analysis was performed using Prism software\\n(GraphPad software, LLC).\\nData availability\\nAll data are contained within the manuscript.\\nSupporting information—This article contains supporting informa-\\ntion (62).\\nAcknowledgments—Recombinant protein for biophysical studies\\nreported in this publication was generated with supported from the\\nUniversity of Michigan Center for Structural Biology (CSB). The\\nCSB acknowledges support from the U-M Life Sciences Institute,\\nthe U-M Rogel Cancer Center, the U-M Medical School Endow-\\nment for Basic Sciences, and grants from the NIH. We thank the\\nUniversity of Michigan BioNMR Core fand for assistance per-\\nforming, analyzing, and interpreting NMR studies. The University\\nof Michigan BioNMR Core is supported by the U-M College of\\nLiterature, Sciences and Arts, Life Sciences Institute, College of'),\n",
|
53 |
+
" Document(id='6a25243d-4b08-4b3d-8db6-d900d6a616ed', metadata={'source': '../data/main.pdf', 'page': 10}, page_content='PAQDVPRSSAKPSIRCFIKPTETLERSLEMNKHKGKKRM\\nQKRPNYKNVGEEEDEERGSAEDAQEDAEKTKGTEGGSKS\\nMKTSGEREEIEMVIMKLGKLSEVAAAGTSVQEQNTTDEE\\nKSAATNSEN\\nShuffle IDPR:\\nMARDKMESNNKTSACSEITGEPETQASREQKVDESEQA\\nEKTDGPNDEMSEAIVAKVLRKNVKKPFKKTREEELLKMN\\nMGASRITNQHKKAYSSLGEEPIGGEARRKGESAPETEKDG\\nEETGSQSTV\\nIDPR K-to-R:\\nPAQDVPRSSARPSIRCFIRPTETLERSLEMNRHRGRRRM\\nQRRPNYRNVGEEEDEERGSAEDAQEDAERTRGTEGGSRS\\nMRTSGEREEIEMVIMRLGRLSEVAAAGTSVQEQNTTDEE\\nRSAATNSEN\\nShuffle IDPR retain K:\\nRNF41 regulates CLEC16A stabilityvia an IDPR\\nJ. Biol. Chem. (2023) 299(4) 103057 11'),\n",
|
54 |
+
" Document(id='eda80cfe-dd68-40e5-84e1-b64db1e9e93d', metadata={'source': '../data/main.pdf', 'page': 12}, page_content='forming, analyzing, and interpreting NMR studies. The University\\nof Michigan BioNMR Core is supported by the U-M College of\\nLiterature, Sciences and Arts, Life Sciences Institute, College of\\nPharmacy, and the Medical School along with the U-M Biosciences\\nInitiative. We thank Drs. H. Popelka, P. Arvan, D. Fingar, and\\nmembers of the Soleimanpour laboratory for helpful advice.\\nAuthor contributions—M. A. G. and S. A. S. conceptualization; M.\\nA. G., J. Z., B. C., M. P. V., N. X., V. S., and D. S. investigation;\\nM. A. G., M. P. V., and D. S. formal analysis; M. A. G. data curation;\\nM. A. G. and S. A. S. writing– original draft; M. A. G. and S. A. S.\\nfunding acquisition; J. Z., B. C., M. P. V., V. S., N. A. K., D. S., D. J.\\nK., S. S., and S. A. S. writing– review and editing; N. A. K., D. S., D. J.\\nK., S. S., and S. A. S. resources; N. A. K., D. S., D. J. K., S. S., and S. A.\\nS. supervision; S. A. S. visualization.\\nFunding and additional information—M. A. G. was supported by'),\n",
|
55 |
+
" Document(id='fa72767a-90cc-4ca0-9889-d4eaf81549e5', metadata={'source': '../data/main.pdf', 'page': 12}, page_content='Protein Sci. 25, 1767–1785\\n20. Guharoy, M., Bhowmick, P., and Tompa, P. (2016) Design principles\\ninvolving protein disorder facilitate specific substrate selection and degra-\\ndation by the ubiquitin-proteasome system.J. Biol. Chem.291,6 7 2 3–6731\\n21. Bhowmick, P., Pancsa, R., Guharoy, M., and Tompa, P. (2013) Functional\\ndiversity and structural disorder in the human ubiquitination pathway.\\nPLoS One8, e65443\\n22. Tunyasuvunakool, K., Adler, J., Wu, Z., Green, T., Zielinski, M.,/C20Zídek, A.,\\net al. (2021) Highly accurate protein structure prediction for the human\\nproteome. Nature 596, 590–596\\n23. Varadi, M., Anyango, S., Deshpande, M., Nair, S., Natassia, C., Yorda-\\nnova, G., et al. (2022) AlphaFold protein structure database: Massively\\nRNF41 regulates CLEC16A stabilityvia an IDPR\\nJ. Biol. Chem. (2023) 299(4) 103057 13')],\n",
|
56 |
+
" 'answer': 'This paper investigates the regulation of CLEC16A stability by RNF41 through an intrinsically disordered protein region (IDPR). It explores the mechanisms by which protein disorder contributes to substrate selection and degradation processes in the ubiquitin-proteasome system. The study involves biophysical and structural analyses supported by various University of Michigan resources.'}"
|
57 |
+
]
|
58 |
+
},
|
59 |
+
"execution_count": 5,
|
60 |
+
"metadata": {},
|
61 |
+
"output_type": "execute_result"
|
62 |
+
}
|
63 |
+
],
|
64 |
+
"source": [
|
65 |
+
"from langchain.chains import create_retrieval_chain\n",
|
66 |
+
"from langchain.chains.combine_documents import create_stuff_documents_chain\n",
|
67 |
+
"from langchain_core.prompts import ChatPromptTemplate\n",
|
68 |
+
"\n",
|
69 |
+
"system_prompt = (\n",
|
70 |
+
" \"You are an assistant for question-answering tasks. \"\n",
|
71 |
+
" \"Use the following pieces of retrieved context to answer \"\n",
|
72 |
+
" \"the question. If you don't know the answer, say that you \"\n",
|
73 |
+
" \"don't know. Use three sentences maximum and keep the \"\n",
|
74 |
+
" \"answer concise.\"\n",
|
75 |
+
" \"\\n\\n\"\n",
|
76 |
+
" \"{context}\"\n",
|
77 |
+
")\n",
|
78 |
+
"\n",
|
79 |
+
"prompt = ChatPromptTemplate.from_messages(\n",
|
80 |
+
" [\n",
|
81 |
+
" (\"system\", system_prompt),\n",
|
82 |
+
" (\"human\", \"{input}\"),\n",
|
83 |
+
" ]\n",
|
84 |
+
")\n",
|
85 |
+
"\n",
|
86 |
+
"\n",
|
87 |
+
"question_answer_chain = create_stuff_documents_chain(llm, prompt)\n",
|
88 |
+
"rag_chain = create_retrieval_chain(retriever, question_answer_chain)\n",
|
89 |
+
"\n",
|
90 |
+
"results = rag_chain.invoke({\"input\": \"What is this paper about?\"})\n",
|
91 |
+
"\n"
|
92 |
+
]
|
93 |
+
},
|
94 |
+
{
|
95 |
+
"cell_type": "code",
|
96 |
+
"execution_count": 9,
|
97 |
+
"metadata": {},
|
98 |
+
"outputs": [
|
99 |
+
{
|
100 |
+
"name": "stdout",
|
101 |
+
"output_type": "stream",
|
102 |
+
"text": [
|
103 |
+
"This paper investigates the regulation of CLEC16A stability by RNF41 through an intrinsically disordered protein region (IDPR). It explores the mechanisms by which protein disorder contributes to substrate selection and degradation processes in the ubiquitin-proteasome system. The study involves biophysical and structural analyses supported by various University of Michigan resources.\n"
|
104 |
+
]
|
105 |
+
}
|
106 |
+
],
|
107 |
+
"source": [
|
108 |
+
"print(results['answer'])"
|
109 |
+
]
|
110 |
+
},
|
111 |
+
{
|
112 |
+
"cell_type": "code",
|
113 |
+
"execution_count": 11,
|
114 |
+
"metadata": {},
|
115 |
+
"outputs": [
|
116 |
+
{
|
117 |
+
"name": "stdout",
|
118 |
+
"output_type": "stream",
|
119 |
+
"text": [
|
120 |
+
"Question: What role does CLEC16A play in mitochondrial quality control, and why is this important for cellular health?\n",
|
121 |
+
"Answer: CLEC16A is an E3 ubiquitin ligase that regulates mitochondrial quality control by facilitating mitophagy, a process that eliminates damaged mitochondria. It forms a complex with RNF41 and USP8 to control the activity of the mitophagy regulator PRKN/Parkin. This function is crucial for cellular health as it maintains mitochondrial integrity, which is vital for energy production and preventing cellular damage, especially in cell types like pancreatic β-cells, sensory neurons, and immune cells.\n",
|
122 |
+
"\n",
|
123 |
+
"Question: How does the intrinsically disordered protein region (IDPR) within CLEC16A impact its stability and interaction with RNF41?\n",
|
124 |
+
"Answer: The intrinsically disordered protein region (IDPR) within CLEC16A is crucial for its stability and interaction with RNF41, as it regulates CLEC16A turnover and is the site where RNF41 acts to destabilize CLEC16A. The IDPR is essential for the enzymatic function and molecular interactions of CLEC16A, including the assembly of the CLEC16A–RNF41–USP8 mitophagy complex. Loss of this IDPR impairs CLEC16A's ability to ubiquitinate RNF41, affecting the overall stability and function of the protein complex.\n",
|
125 |
+
"\n",
|
126 |
+
"Question: What is the significance of the CLEC16A-RNF41 complex in the regulation of mitophagy?\n",
|
127 |
+
"Answer: The CLEC16A-RNF41 complex is significant in the regulation of mitophagy as it promotes the assembly and stability of the tripartite mitophagy complex, which includes CLEC16A, RNF41, and USP8. This complex plays a crucial role in mitochondrial quality control, and the loss of CLEC16A impairs mitochondrial health and function in various cell types. Additionally, RNF41 might have a more central role in mitophagy than previously thought, as it can lead to the degradation of key mitophagy regulators.\n",
|
128 |
+
"\n",
|
129 |
+
"Question: How does RNF41 influence the turnover of CLEC16A, and what are the molecular mechanisms involved?\n",
|
130 |
+
"Answer: RNF41 influences the turnover of CLEC16A by ubiquitinating and destabilizing it, an action that requires RNF41's ubiquitin ligase activity. Overexpression of RNF41 decreases CLEC16A protein levels and increases its ubiquitination, but a ligase-dead RNF41 mutant does not affect CLEC16A levels. The internal IDPR of CLEC16A is crucial for this process, as altering this region prevents RNF41 from reducing CLEC16A levels.\n",
|
131 |
+
"\n",
|
132 |
+
"Question: Which diseases are associated with dysregulation of CLEC16A, and what implications does this have for potential treatments?\n",
|
133 |
+
"Answer: Dysregulation of CLEC16A is associated with over 20 human diseases, including diabetes, cardiovascular disease, stroke, multiple sclerosis, arthritis, Crohn's disease, and other inflammatory diseases. The implications for potential treatments involve targeting the intrinsic disordered protein region (IDPR) within CLEC16A to prevent its turnover, thereby increasing protein levels to enhance its function. This approach could potentially treat or prevent diseases associated with reduced CLEC16A levels by improving mitochondrial health through enhanced mitophagy.\n",
|
134 |
+
"\n",
|
135 |
+
"Question: What techniques were used in this study to confirm the presence and function of the IDPR in CLEC16A?\n",
|
136 |
+
"Answer: The study used in silico computational techniques, including AlphaFold for protein structure prediction and IUPred2 for disorder prediction, to identify the putative internal IDPR in CLEC16A. Experimentally, they used NMR spectroscopy to examine the structural conformation of the IDPR and Western blot analysis to assess the impact of the IDPR on protein stability. They also introduced mutations and compared protein levels and stability in HEK293T cells to determine the function of the IDPR in regulating CLEC16A stability.\n",
|
137 |
+
"\n",
|
138 |
+
"Question: How does the disruption of CLEC16A’s IDPR affect its ubiquitination and degradation?\n",
|
139 |
+
"Answer: Disruption of CLEC16A's internal IDPR prevents its turnover and reduces self-ubiquitination in vitro. The IDPR promotes CLEC16A destabilization, and its absence results in higher stability and protein levels compared to the wild-type CLEC16A. Additionally, RNF41 promotes the ubiquitination and destabilization of CLEC16A, and this process is impaired when the IDPR is disrupted.\n",
|
140 |
+
"\n",
|
141 |
+
"Question: Why might the IDPR in CLEC16A be considered a therapeutic target for diseases related to mitochondrial dysfunction?\n",
|
142 |
+
"Answer: The IDPR in CLEC16A is considered a therapeutic target because it regulates CLEC16A turnover, and its destabilization can impact mitochondrial health. By blocking access to this region, it is possible to prevent CLEC16A turnover, potentially increasing protein levels and enhancing its function. This could help treat or prevent diseases associated with mitochondrial dysfunction.\n",
|
143 |
+
"\n",
|
144 |
+
"Question: How do mutations within CLEC16A’s IDPR affect the protein's ability to form complexes with RNF41 and USP8?\n",
|
145 |
+
"Answer: Mutations within CLEC16A's IDPR impair the protein's ability to bind and ubiquitinate RNF41, which is essential for forming the CLEC16A–RNF41–USP8 mitophagy complex. Truncating or shuffling the residues within the IDPR reduces its binding to RNF41 and disrupts the assembly of the tripartite complex. This suggests that the integrity and specific sequence of the IDPR are crucial for the proper interaction and complex formation with RNF41 and USP8.\n",
|
146 |
+
"\n",
|
147 |
+
"Question: What did biophysical analyses reveal about the structural properties of CLEC16A’s IDPR, and how do these properties contribute to its function?\n",
|
148 |
+
"Answer: Biophysical analyses revealed that CLEC16A's internal IDPR is predicted to lack secondary structure and is enriched in charged, polar residues like glutamic acid and lysine, which promote intrinsic disorder. These structural properties contribute to the protein's function by regulating CLEC16A turnover, as lysine residues in the IDPR are essential for this process. The IDPR's role in turnover is significant because it affects CLEC16A stability and function, which is pertinent to its involvement in human diseases.\n",
|
149 |
+
"\n"
|
150 |
+
]
|
151 |
+
}
|
152 |
+
],
|
153 |
+
"source": [
|
154 |
+
"# List of questions based on the PDF content for testing\n",
|
155 |
+
"questions = [\n",
|
156 |
+
" \"What role does CLEC16A play in mitochondrial quality control, and why is this important for cellular health?\",\n",
|
157 |
+
" \"How does the intrinsically disordered protein region (IDPR) within CLEC16A impact its stability and interaction with RNF41?\",\n",
|
158 |
+
" \"What is the significance of the CLEC16A-RNF41 complex in the regulation of mitophagy?\",\n",
|
159 |
+
" \"How does RNF41 influence the turnover of CLEC16A, and what are the molecular mechanisms involved?\",\n",
|
160 |
+
" \"Which diseases are associated with dysregulation of CLEC16A, and what implications does this have for potential treatments?\",\n",
|
161 |
+
" \"What techniques were used in this study to confirm the presence and function of the IDPR in CLEC16A?\",\n",
|
162 |
+
" \"How does the disruption of CLEC16A’s IDPR affect its ubiquitination and degradation?\",\n",
|
163 |
+
" \"Why might the IDPR in CLEC16A be considered a therapeutic target for diseases related to mitochondrial dysfunction?\",\n",
|
164 |
+
" \"How do mutations within CLEC16A’s IDPR affect the protein's ability to form complexes with RNF41 and USP8?\",\n",
|
165 |
+
" \"What did biophysical analyses reveal about the structural properties of CLEC16A’s IDPR, and how do these properties contribute to its function?\"\n",
|
166 |
+
"]\n",
|
167 |
+
"\n",
|
168 |
+
"# Loop through each question, invoke the RAG chain, and print each answer\n",
|
169 |
+
"for question in questions:\n",
|
170 |
+
" result = rag_chain.invoke({\"input\": question})\n",
|
171 |
+
" print(f\"Question: {question}\")\n",
|
172 |
+
" print(f\"Answer: {result[\"answer\"]}\\n\")\n"
|
173 |
+
]
|
174 |
+
},
|
175 |
+
{
|
176 |
+
"cell_type": "code",
|
177 |
+
"execution_count": null,
|
178 |
+
"metadata": {},
|
179 |
+
"outputs": [],
|
180 |
+
"source": []
|
181 |
+
}
|
182 |
+
],
|
183 |
+
"metadata": {
|
184 |
+
"kernelspec": {
|
185 |
+
"display_name": "venv",
|
186 |
+
"language": "python",
|
187 |
+
"name": "python3"
|
188 |
+
},
|
189 |
+
"language_info": {
|
190 |
+
"codemirror_mode": {
|
191 |
+
"name": "ipython",
|
192 |
+
"version": 3
|
193 |
+
},
|
194 |
+
"file_extension": ".py",
|
195 |
+
"mimetype": "text/x-python",
|
196 |
+
"name": "python",
|
197 |
+
"nbconvert_exporter": "python",
|
198 |
+
"pygments_lexer": "ipython3",
|
199 |
+
"version": "3.13.0"
|
200 |
+
}
|
201 |
+
},
|
202 |
+
"nbformat": 4,
|
203 |
+
"nbformat_minor": 2
|
204 |
+
}
|
todo.md
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
https://medium.com/@atul.auddy/question-answering-over-documents-using-%EF%B8%8Flangchain-and-pinecone-30250391d6a5
|
2 |
+
read this to know how to do rag pinecone and langchain on docs
|
utils/__init__.py
ADDED
File without changes
|
utils/helpers.py
ADDED
File without changes
|
utils/models.py
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from sqlalchemy import create_engine, Column, Integer, String, Text, DateTime, ForeignKey, Boolean
|
2 |
+
from sqlalchemy.ext.declarative import declarative_base
|
3 |
+
from sqlalchemy.orm import relationship, sessionmaker
|
4 |
+
from datetime import datetime
|
5 |
+
import uuid
|
6 |
+
|
7 |
+
Base = declarative_base()
|
8 |
+
|
9 |
+
class ChatSession(Base):
|
10 |
+
__tablename__ = 'chat_sessions'
|
11 |
+
|
12 |
+
id = Column(Integer, primary_key=True)
|
13 |
+
session_id = Column(String(36), unique=True, default=lambda: str(uuid.uuid4()))
|
14 |
+
doctor_name = Column(String(100), nullable=False) # Added doctor name
|
15 |
+
user_identifier = Column(String(150)) # Will store doctor_name + timestamp
|
16 |
+
started_at = Column(DateTime, default=datetime.utcnow)
|
17 |
+
last_activity = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
18 |
+
|
19 |
+
messages = relationship("ChatMessage", back_populates="session")
|
20 |
+
|
21 |
+
@classmethod
|
22 |
+
def get_sessions_by_doctor(cls, session, doctor_name):
|
23 |
+
"""Get all sessions for a specific doctor"""
|
24 |
+
return session.query(cls).filter(cls.doctor_name == doctor_name).all()
|
25 |
+
|
26 |
+
class ChatMessage(Base):
|
27 |
+
__tablename__ = 'chat_messages'
|
28 |
+
|
29 |
+
id = Column(Integer, primary_key=True)
|
30 |
+
session_id = Column(String(36), ForeignKey('chat_sessions.session_id'))
|
31 |
+
timestamp = Column(DateTime, default=datetime.utcnow)
|
32 |
+
is_user = Column(Boolean, default=True)
|
33 |
+
message = Column(Text)
|
34 |
+
sources_used = Column(Text, nullable=True)
|
35 |
+
|
36 |
+
session = relationship("ChatSession", back_populates="messages")
|
37 |
+
|
38 |
+
class DatabaseManager:
|
39 |
+
def __init__(self, db_url="sqlite:///chat_history.db"):
|
40 |
+
self.engine = create_engine(db_url)
|
41 |
+
Base.metadata.create_all(self.engine)
|
42 |
+
self.Session = sessionmaker(bind=self.engine)
|
43 |
+
|
44 |
+
def create_session(self, doctor_name):
|
45 |
+
"""Create a new chat session with doctor name and timestamp"""
|
46 |
+
session = self.Session()
|
47 |
+
try:
|
48 |
+
# Create unique timestamp for this session
|
49 |
+
timestamp = datetime.utcnow().strftime("%Y%m%d_%H%M%S_%f") # Added milliseconds
|
50 |
+
user_identifier = f"{doctor_name}_{timestamp}"
|
51 |
+
|
52 |
+
chat_session = ChatSession(
|
53 |
+
doctor_name=doctor_name,
|
54 |
+
user_identifier=user_identifier
|
55 |
+
)
|
56 |
+
session.add(chat_session)
|
57 |
+
session.commit()
|
58 |
+
return chat_session.session_id
|
59 |
+
finally:
|
60 |
+
session.close()
|
61 |
+
|
62 |
+
def get_doctor_sessions(self, doctor_name):
|
63 |
+
"""Get all sessions for a specific doctor"""
|
64 |
+
session = self.Session()
|
65 |
+
try:
|
66 |
+
return session.query(ChatSession)\
|
67 |
+
.filter(ChatSession.doctor_name == doctor_name)\
|
68 |
+
.order_by(ChatSession.last_activity.desc()).all()
|
69 |
+
finally:
|
70 |
+
session.close()
|
71 |
+
|
72 |
+
def log_message(self, session_id, message, is_user=True, sources=None):
|
73 |
+
session = self.Session()
|
74 |
+
try:
|
75 |
+
chat_message = ChatMessage(
|
76 |
+
session_id=session_id,
|
77 |
+
message=message,
|
78 |
+
is_user=is_user,
|
79 |
+
sources_used=sources
|
80 |
+
)
|
81 |
+
session.add(chat_message)
|
82 |
+
session.commit()
|
83 |
+
finally:
|
84 |
+
session.close()
|
85 |
+
|
86 |
+
def get_session_history(self, session_id):
|
87 |
+
session = self.Session()
|
88 |
+
try:
|
89 |
+
messages = session.query(ChatMessage)\
|
90 |
+
.filter(ChatMessage.session_id == session_id)\
|
91 |
+
.order_by(ChatMessage.timestamp).all()
|
92 |
+
return messages
|
93 |
+
finally:
|
94 |
+
session.close()
|