stivenDR14 commited on
Commit
67f0d18
·
0 Parent(s):

Initial commit

Browse files
Files changed (6) hide show
  1. .github/workflows/manual.yml +20 -0
  2. .gitignore +14 -0
  3. README.md +141 -0
  4. app.py +246 -0
  5. pdf_processor.py +353 -0
  6. utils.py +190 -0
.github/workflows/manual.yml ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face hub
2
+ on:
3
+ push:
4
+ branches: [main]
5
+
6
+ # to run this workflow manually from the Actions tab
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ sync-to-hub:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v3
14
+ with:
15
+ fetch-depth: 0
16
+ lfs: true
17
+ - name: Push to hub
18
+ env:
19
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
20
+ run: git push https://HF_USERNAME:[email protected]/spaces/stiv14/pdf-multilanguage-qa-role main
.gitignore ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #ignore env variables
2
+ .env
3
+
4
+ #ignore pycache folder and all files in it
5
+ __pycache__
6
+
7
+ #ignore chroma_db folder
8
+ chroma_db
9
+
10
+ #ignore ollama folder
11
+ ollama
12
+
13
+ #ignore llama_index folder
14
+ llama_index
README.md ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🤖 PDF AI Assistant
2
+
3
+ A multilingual PDF processing application that leverages various AI models to analyze, summarize, and interact with PDF documents. Built with Python, Gradio, and LangChain.
4
+
5
+ ## 🌟 Features
6
+
7
+ - **Multiple AI Models Support**:
8
+
9
+ - OpenAI GPT-4
10
+ - IBM Granite 3.1
11
+ - Mistral Small 24B
12
+ - SmolLM2 1.7B
13
+ - Local Ollama models
14
+
15
+ - **Multilingual Interface**:
16
+
17
+ - English
18
+ - Español
19
+ - Deutsch
20
+ - Français
21
+ - Português
22
+
23
+ - **Core Functionalities**:
24
+ - 📝 Text extraction from PDFs
25
+ - 💬 Interactive Q&A with document content
26
+ - 📋 Document summarization
27
+ - 👨‍💼 Customizable specialist advisor
28
+ - 🔄 Dynamic chunk size and overlap settings
29
+
30
+ ## 🛠️ Installation
31
+
32
+ 1. Clone the repository:
33
+
34
+ ```bash
35
+ git clone <repository-url>
36
+ cd pdf-ai-assistant
37
+ ```
38
+
39
+ 2. Install required dependencies:
40
+
41
+ ```bash
42
+ pip install -r requirements.txt
43
+ ```
44
+
45
+ 3. Set up environment variables:
46
+
47
+ ```bash
48
+ # Create .env file
49
+ touch .env
50
+
51
+ # Add your API keys (if using)
52
+ WATSONX_APIKEY=your_watsonx_api_key
53
+ WATSONX_PROJECT_ID=your_watsonx_project_id
54
+ ```
55
+
56
+ ## 📦 Dependencies
57
+
58
+ - gradio
59
+ - langchain
60
+ - chromadb
61
+ - PyPDF2
62
+ - ollama (for local models)
63
+ - python-dotenv
64
+ - requests
65
+ - ibm-watsonx-ai
66
+
67
+ ## 🚀 Usage
68
+
69
+ 1. Start the application:
70
+
71
+ ```bash
72
+ python app.py
73
+ ```
74
+
75
+ 2. Open your web browser and navigate to the provided URL (usually http://localhost:7860)
76
+
77
+ 3. Select your preferred:
78
+
79
+ - Language
80
+ - AI Model
81
+ - Model Type (Local/API)
82
+
83
+ 4. Upload a PDF file and process it
84
+
85
+ 5. Use any of the three main features:
86
+ - Ask questions about the document
87
+ - Generate a comprehensive summary
88
+ - Get specialized analysis using the custom advisor
89
+
90
+ ## 💡 Features in Detail
91
+
92
+ ### Q&A System
93
+
94
+ - Interactive chat interface
95
+ - Context-aware responses
96
+ - Source page references
97
+
98
+ ### Summarization
99
+
100
+ - Chunk-based processing
101
+ - Configurable chunk sizes
102
+ - Comprehensive document overview
103
+
104
+ ### Specialist Advisor
105
+
106
+ - Customizable expert roles
107
+ - Detailed analysis based on expertise
108
+ - Structured insights and recommendations
109
+
110
+ ## 🔧 Configuration
111
+
112
+ The application supports various AI models:
113
+
114
+ - Local models via Ollama
115
+ - API-based models (OpenAI, IBM WatsonX)
116
+ - Hugging Face models
117
+
118
+ For Ollama local models, ensure:
119
+
120
+ ```bash
121
+ ollama pull granite3.1-dense
122
+ ollama pull granite-embedding:278m
123
+ ```
124
+
125
+ ## 🌐 Language Support
126
+
127
+ The interface and AI responses are available in:
128
+
129
+ - English
130
+ - Spanish
131
+ - German
132
+ - French
133
+ - Portuguese
134
+
135
+ ## 📝 License
136
+
137
+ [MIT License]
138
+
139
+ ## 🤝 Contributing
140
+
141
+ Contributions, issues, and feature requests are welcome!
app.py ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from pdf_processor import PDFProcessor
3
+ from utils import AI_MODELS, TRANSLATIONS
4
+
5
+ class PDFProcessorUI:
6
+ def __init__(self):
7
+ self.processor = PDFProcessor()
8
+ self.current_language = "English"
9
+ self.current_ai_model = "Huggingface / IBM granite granite 3.1 8b Instruct"
10
+ self.current_type_model = "Api Key"
11
+
12
+ def change_language(self, language):
13
+ self.current_language = language
14
+ self.processor.set_language(language)
15
+
16
+ # Retornamos todos los textos que necesitan ser actualizados
17
+ return [
18
+ TRANSLATIONS[language]["title"],
19
+ gr.update(label=TRANSLATIONS[language]["upload_pdf"]),
20
+ gr.update(label=TRANSLATIONS[language]["chunk_size"]),
21
+ gr.update(label=TRANSLATIONS[language]["chunk_overlap"]),
22
+ gr.update(value=TRANSLATIONS[language]["process_btn"]),
23
+ gr.update(label=TRANSLATIONS[language]["processing_status"]),
24
+ gr.update(label=TRANSLATIONS[language]["qa_tab"]),
25
+ gr.update(label=TRANSLATIONS[language]["summary_tab"]),
26
+ gr.update(label=TRANSLATIONS[language]["specialist_tab"]),
27
+ gr.update(label=TRANSLATIONS[language]["mini_summary_title"]),
28
+ gr.update(label=TRANSLATIONS[language]["mini_analysis_title"]),
29
+ gr.update(placeholder=TRANSLATIONS[language]["chat_placeholder"]),
30
+ TRANSLATIONS[language]["chat_title"],
31
+ gr.update(value=TRANSLATIONS[language]["chat_btn"]),
32
+ gr.update(value=TRANSLATIONS[language]["generate_summary"]),
33
+ gr.update(label=TRANSLATIONS[language]["summary_label"]),
34
+ gr.update(label=TRANSLATIONS[language]["ai_model"]),
35
+ TRANSLATIONS[language]["specialist_title"],
36
+ gr.update(label=TRANSLATIONS[language]["specialist_label"]),
37
+ gr.update(label=TRANSLATIONS[language]["specialist_output"]),
38
+ gr.update(value=TRANSLATIONS[language]["specialist_btn"])
39
+ ]
40
+
41
+ def change_ai_model(self, ai_model):
42
+ self.current_ai_model = ai_model
43
+ if ai_model == "IBM Granite3.1 dense / Ollama local":
44
+ return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False, maximum=2048), gr.update(visible=False, maximum=200)
45
+ elif ai_model == "Open AI / GPT-4o-mini":
46
+ return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False), gr.update(visible=False, maximum=2048), gr.update(visible=False, maximum=200)
47
+ else:
48
+ return gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False, maximum=500), gr.update(visible=False, maximum=100)
49
+
50
+ def change_type_model(self, type_model):
51
+ self.current_type_model = type_model
52
+ if type_model == "Api Key":
53
+ if self.current_ai_model == "IBM Granite3.1 dense / Ollama local":
54
+ return gr.update(visible=False), gr.update(visible=False)
55
+ else:
56
+ return gr.update(visible=True), gr.update(visible=False)
57
+ else:
58
+ return gr.update(visible=False), gr.update(visible=False)
59
+
60
+ def process_pdf(self, pdf_file, chunk_size, chunk_overlap, ai_model, type_model, api_key, project_id_watsonx):
61
+ return self.processor.process_pdf(pdf_file, chunk_size, chunk_overlap, ai_model, type_model, api_key, project_id_watsonx)
62
+
63
+ def qa_interface(self, message, history, ai_model, type_model, api_key, project_id_watsonx):
64
+ return self.processor.get_qa_response(message, history, ai_model, type_model, api_key, project_id_watsonx)
65
+
66
+ def summarize_interface(self, ai_model, type_model, api_key, project_id_watsonx):
67
+ return self.processor.get_summary(ai_model, type_model, api_key, project_id_watsonx)
68
+
69
+ def specialist_opinion(self, ai_model, type_model, api_key, project_id_watsonx, specialist_prompt):
70
+ return self.processor.get_specialist_opinion(ai_model, type_model, api_key, project_id_watsonx, specialist_prompt)
71
+
72
+ def upload_file(files):
73
+ file_paths = [file.name for file in files]
74
+ return file_paths[0]
75
+
76
+ def create_ui(self):
77
+ with gr.Blocks() as demo:
78
+ title = gr.Markdown(TRANSLATIONS[self.current_language]["title"])
79
+
80
+ with gr.Row():
81
+ language_dropdown = gr.Dropdown(
82
+ choices=list(TRANSLATIONS.keys()),
83
+ value=self.current_language,
84
+ label="Language/Idioma/Sprache/Langue/Língua",
85
+ key="language_dropdown"
86
+ )
87
+ ai_model_dropdown = gr.Dropdown(
88
+ choices=list(AI_MODELS.keys()),
89
+ value=self.current_ai_model,
90
+ label= TRANSLATIONS[self.current_language]["ai_model"],
91
+ key="ai_model_dropdown"
92
+ )
93
+
94
+ with gr.Row():
95
+ with gr.Column():
96
+ with gr.Row():
97
+ pdf_file = gr.File(
98
+ label=TRANSLATIONS[self.current_language]["upload_pdf"],
99
+ file_types=[".pdf"]
100
+ )
101
+ with gr.Column():
102
+ type_model=gr.Radio(choices=["Local", "Api Key"], label=TRANSLATIONS[self.current_language]["model_type"], visible=False, value="Api Key")
103
+ api_key_input = gr.Textbox(label="Api Key", placeholder=TRANSLATIONS[self.current_language]["api_key_placeholder"], visible=False)
104
+ project_id_watsonx = gr.Textbox(label="Project ID", placeholder=TRANSLATIONS[self.current_language]["project_id_placeholder"], visible=False)
105
+ chunk_size = gr.Slider(
106
+ value=250,
107
+ label=TRANSLATIONS[self.current_language]["chunk_size"],
108
+ minimum=100,
109
+ maximum=500,
110
+ step=10,
111
+ visible=False
112
+ )
113
+ chunk_overlap = gr.Slider(
114
+ value=25,
115
+ label=TRANSLATIONS[self.current_language]["chunk_overlap"],
116
+ minimum=10,
117
+ maximum=100,
118
+ step=5,
119
+ visible=False
120
+ )
121
+ process_btn = gr.Button(
122
+ TRANSLATIONS[self.current_language]["process_btn"]
123
+ )
124
+ process_output = gr.Textbox(
125
+ label=TRANSLATIONS[self.current_language]["processing_status"]
126
+ )
127
+
128
+ with gr.Tabs() as tabs:
129
+ qa_tab = gr.Tab(TRANSLATIONS[self.current_language]["qa_tab"])
130
+ summary_tab = gr.Tab(TRANSLATIONS[self.current_language]["summary_tab"])
131
+ specialist_tab = gr.Tab(TRANSLATIONS[self.current_language]["specialist_tab"])
132
+ with qa_tab:
133
+ chat_title = gr.Markdown(TRANSLATIONS[self.current_language]["chat_title"])
134
+ chat_placeholder = gr.Textbox(
135
+ placeholder=TRANSLATIONS[self.current_language]["chat_placeholder"],
136
+ container=False,
137
+ show_label=False
138
+ )
139
+ chat_btn = gr.Button(TRANSLATIONS[self.current_language]["chat_btn"])
140
+ chatbot = gr.Markdown(height=400)
141
+
142
+ with summary_tab:
143
+ with gr.Accordion(TRANSLATIONS[self.current_language]["mini_analysis_title"], open=False, visible=False):
144
+ minisummaries_output = gr.Textbox(
145
+ label=TRANSLATIONS[self.current_language]["mini_analysis_title"],
146
+ lines=10
147
+ )
148
+ summary_output = gr.Textbox(
149
+ label=TRANSLATIONS[self.current_language]["summary_label"],
150
+ lines=10
151
+ )
152
+ summarize_btn = gr.Button(
153
+ TRANSLATIONS[self.current_language]["generate_summary"]
154
+ )
155
+
156
+ with specialist_tab:
157
+ specialist_title = gr.Markdown(TRANSLATIONS[self.current_language]["specialist_title"])
158
+ specialist_placeholder = gr.Textbox(
159
+ label=TRANSLATIONS[self.current_language]["specialist_label"],
160
+ lines=10
161
+ )
162
+ with gr.Accordion(TRANSLATIONS[self.current_language]["mini_analysis_title"], open=False, visible=False):
163
+ minianalysis_output = gr.Textbox(
164
+ label=TRANSLATIONS[self.current_language]["mini_analysis_title"],
165
+ lines=10
166
+ )
167
+ specialist_output = gr.Textbox(label=TRANSLATIONS[self.current_language]["specialist_output"], lines=20)
168
+ specialist_btn = gr.Button(TRANSLATIONS[self.current_language]["specialist_btn"])
169
+
170
+
171
+ language_dropdown.change(
172
+ fn=self.change_language,
173
+ inputs=[language_dropdown],
174
+ outputs=[
175
+ title,
176
+ pdf_file,
177
+ chunk_size,
178
+ chunk_overlap,
179
+ process_btn,
180
+ process_output,
181
+ qa_tab,
182
+ summary_tab,
183
+ specialist_tab,
184
+ minisummaries_output,
185
+ minianalysis_output,
186
+ chat_placeholder,
187
+ chat_title,
188
+ chat_btn,
189
+ summarize_btn,
190
+ summary_output,
191
+ ai_model_dropdown,
192
+ specialist_title,
193
+ specialist_placeholder,
194
+ specialist_output,
195
+ specialist_btn
196
+ ]
197
+ )
198
+
199
+ ai_model_dropdown.change(
200
+ fn=self.change_ai_model,
201
+ inputs=[ai_model_dropdown],
202
+ outputs=[type_model, api_key_input, project_id_watsonx, chunk_size, chunk_overlap]
203
+ )
204
+
205
+ type_model.change(
206
+ fn=self.change_type_model,
207
+ inputs=[type_model],
208
+ outputs=[api_key_input,project_id_watsonx]
209
+ )
210
+
211
+ chat_placeholder.submit(
212
+ fn=self.qa_interface,
213
+ inputs=[chat_placeholder, chatbot, ai_model_dropdown, type_model, api_key_input, project_id_watsonx],
214
+ outputs=[chatbot]
215
+ )
216
+
217
+ process_btn.click(
218
+ fn=self.process_pdf,
219
+ inputs=[pdf_file, chunk_size, chunk_overlap, ai_model_dropdown, type_model, api_key_input, project_id_watsonx],
220
+ outputs=[process_output]
221
+ )
222
+
223
+ summarize_btn.click(
224
+ fn=self.summarize_interface,
225
+ inputs=[ai_model_dropdown, type_model, api_key_input, project_id_watsonx],
226
+ outputs=[summary_output]
227
+ )
228
+
229
+ specialist_btn.click(
230
+ fn=self.specialist_opinion,
231
+ inputs=[ai_model_dropdown, type_model, api_key_input, project_id_watsonx, specialist_placeholder],
232
+ outputs=[specialist_output]
233
+ )
234
+
235
+ chat_btn.click(
236
+ fn=self.qa_interface,
237
+ inputs=[chat_placeholder, chatbot, ai_model_dropdown, type_model, api_key_input, project_id_watsonx],
238
+ outputs=[chatbot]
239
+ )
240
+
241
+ return demo
242
+
243
+ if __name__ == "__main__":
244
+ ui = PDFProcessorUI()
245
+ demo = ui.create_ui()
246
+ demo.launch()
pdf_processor.py ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import tempfile
3
+ from langchain_community.document_loaders import PyPDFLoader
4
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
5
+ from langchain_ollama import OllamaEmbeddings
6
+ from langchain_community.vectorstores import Chroma
7
+ from langchain_ollama import OllamaLLM
8
+ from langchain.chains import RetrievalQA
9
+ from langchain.prompts import PromptTemplate
10
+ from langchain_openai import ChatOpenAI, OpenAIEmbeddings
11
+ from ibm_watsonx_ai.metanames import EmbedTextParamsMetaNames
12
+ from langchain_ibm import WatsonxLLM, WatsonxEmbeddings
13
+ from langchain_huggingface import HuggingFaceEndpoint, HuggingFaceEmbeddings
14
+ from ibm_watsonx_ai import APIClient, Credentials
15
+ from utils import AI_MODELS, TRANSLATIONS
16
+ import chromadb
17
+ import requests
18
+ import os
19
+ from dotenv import load_dotenv
20
+
21
+ OLLAMA_LLM = "granite3.1-dense"
22
+ OLLAMA_EMBEDDINGS = "granite-embedding:278m"
23
+
24
+
25
+ load_dotenv()
26
+
27
+ api_key_watsonx = os.getenv('WATSONX_APIKEY')
28
+ projectid_watsonx = os.getenv('WATSONX_PROJECT_ID')
29
+ endpoint_watsonx = "https://us-south.ml.cloud.ibm.com"
30
+
31
+ def set_up_watsonx():
32
+ token_watsonx = authenticate_watsonx(api_key_watsonx)
33
+ if token_watsonx == None:
34
+ return None
35
+ parameters = {
36
+ "max_new_tokens": 1500,
37
+ "min_new_tokens": 1,
38
+ "temperature": 0.7,
39
+ "top_k": 50,
40
+ "top_p": 1,
41
+ }
42
+
43
+ embed_params = {
44
+ EmbedTextParamsMetaNames.TRUNCATE_INPUT_TOKENS: 1,
45
+ EmbedTextParamsMetaNames.RETURN_OPTIONS: {"input_text": True},
46
+ }
47
+
48
+ credentials = Credentials(
49
+ url = endpoint_watsonx,
50
+ api_key = api_key_watsonx,
51
+ )
52
+
53
+ client = APIClient(credentials, project_id=projectid_watsonx)
54
+
55
+ client.set_token(token_watsonx)
56
+
57
+ watsonx_llm = WatsonxLLM(
58
+ model_id="ibm/granite-3-2-8b-instruct",
59
+ watsonx_client=client,
60
+ params = parameters
61
+ )
62
+
63
+
64
+ watsonx_embedding = WatsonxEmbeddings(
65
+ model_id="ibm/granite-embedding-278m-multilingual",
66
+ url=endpoint_watsonx,
67
+ project_id=projectid_watsonx,
68
+ params=embed_params,
69
+ )
70
+
71
+ return watsonx_llm, watsonx_embedding
72
+
73
+ def authenticate_watsonx(api_key):
74
+ url = "https://iam.cloud.ibm.com/identity/token"
75
+ headers = {
76
+ "Content-Type": "application/x-www-form-urlencoded"
77
+ }
78
+ data = {
79
+ "grant_type": "urn:ibm:params:oauth:grant-type:apikey",
80
+ "apikey": api_key
81
+ }
82
+
83
+ response = requests.post(url, headers=headers, data=data)
84
+
85
+ if response.status_code == 200:
86
+ token = response.json().get('access_token')
87
+ os.environ["WATSONX_TOKEN"] = token
88
+ return token
89
+ else:
90
+ print("Authentication failed. Status code:", response.status_code)
91
+ print("Response:", response.text)
92
+ return None
93
+
94
+
95
+ class PDFProcessor:
96
+ def __init__(self):
97
+ self.vectorstore = None
98
+ self.language = "English"
99
+
100
+ def set_language(self, language):
101
+ self.language = language
102
+
103
+ def set_llm(self, ai_model, type_model, api_key, project_id_watsonx):
104
+ if ai_model == "Open AI / GPT-4o-mini":
105
+ current_llm = ChatOpenAI(
106
+ model="gpt-4o",
107
+ temperature=0.5,
108
+ max_tokens=None,
109
+ timeout=None,
110
+ max_retries=2,
111
+ api_key=api_key,
112
+ )
113
+ embeding_model = OpenAIEmbeddings(
114
+ model="text-embedding-3-small",
115
+ api_key=api_key,
116
+ )
117
+
118
+
119
+ elif ai_model == "IBM Granite3.1 dense / Ollama local":
120
+ if type_model == "Local":
121
+ try:
122
+ # Verificar que Ollama está funcionando y el modelo está disponible
123
+ current_llm = OllamaLLM(model=OLLAMA_LLM)
124
+ # Intenta hacer un embedding de prueba
125
+ test_embedding = OllamaEmbeddings(model=OLLAMA_EMBEDDINGS)
126
+ test_embedding.embed_query("test")
127
+ embeding_model = test_embedding
128
+ except Exception as e:
129
+ print(f"Error with Ollama: {e}")
130
+ # Fallback a otro modelo o manejo de error
131
+ raise Exception("Please ensure Ollama is running and the models are pulled: \n" +
132
+ f"ollama pull {OLLAMA_LLM}\n" +
133
+ f"ollama pull {OLLAMA_EMBEDDINGS}")
134
+ else:
135
+ current_llm, embeding_model = set_up_watsonx()
136
+ else:
137
+ current_llm = HuggingFaceEndpoint(
138
+ repo_id= AI_MODELS[ai_model],
139
+ temperature=0.5,
140
+ )
141
+ embeding_model = HuggingFaceEmbeddings(
142
+ model_name="ibm-granite/granite-embedding-278m-multilingual",
143
+ )
144
+ return current_llm, embeding_model
145
+
146
+
147
+ def process_pdf(self, pdf_file, chunk_size, chunk_overlap, ai_model, type_model, api_key, project_id_watsonx):
148
+ defined_chunk_size = 1000
149
+ defined_chunk_overlap = 100
150
+ if (ai_model == "Open AI / GPT-4o-mini" and (api_key == "")) : #or (ai_model == "IBM Granite3.1 dense / Ollama local" and type_model == "Api Key" and (api_key == "" or project_id_watsonx == "")
151
+ return TRANSLATIONS[self.language]["api_key_required"]
152
+ if pdf_file is not None:
153
+ loader = PyPDFLoader(file_path=pdf_file.name)
154
+ documents = loader.load()
155
+ #delete empty page_content documents from documents
156
+ documents = [doc for doc in documents if doc.page_content]
157
+ if(ai_model == "Open AI / GPT-4o-mini" or ai_model == "IBM Granite3.1 dense / Ollama local"):
158
+ if type_model == "Api Key":
159
+ text_splitter = RecursiveCharacterTextSplitter(
160
+ chunk_size=defined_chunk_size,
161
+ chunk_overlap=defined_chunk_overlap,
162
+ separators=["\n\n", "\n"]
163
+ )
164
+ else:
165
+ text_splitter = RecursiveCharacterTextSplitter(
166
+ chunk_size=defined_chunk_size,
167
+ chunk_overlap=defined_chunk_overlap,
168
+ )
169
+ else:
170
+ text_splitter = RecursiveCharacterTextSplitter(
171
+ chunk_size=defined_chunk_size,
172
+ chunk_overlap=defined_chunk_overlap
173
+ )
174
+
175
+ #print(text_splitter)
176
+ texts = text_splitter.split_documents(documents)
177
+ _, embeddings = self.set_llm(ai_model, type_model, api_key, project_id_watsonx)
178
+
179
+ #delete all documents from the vectorstore
180
+ if self.vectorstore:
181
+ self.vectorstore.delete_collection()
182
+
183
+ new_client = chromadb.EphemeralClient()
184
+
185
+ self.vectorstore = Chroma.from_documents(
186
+ documents=texts,
187
+ embedding=embeddings,
188
+ client=new_client,
189
+ collection_name="pdf_collection"
190
+ #persist_directory="./chroma_db"
191
+ )
192
+
193
+ return TRANSLATIONS[self.language]["pdf_processed"] + f" ---- Chunks: {len(self.vectorstore.get()["documents"])}"
194
+
195
+ else:
196
+ return TRANSLATIONS[self.language]["load_pdf_first"]
197
+
198
+
199
+ def get_qa_response(self, message, history, ai_model, type_model, api_key, project_id_watsonx, k=4):
200
+ current_llm, _ = self.set_llm(ai_model, type_model, api_key, project_id_watsonx)
201
+
202
+ if not self.vectorstore:
203
+ return TRANSLATIONS[self.language]["load_pdf_first"]
204
+
205
+ retriever = self.vectorstore.as_retriever(search_kwargs={"k": k})
206
+
207
+ qa_chain = RetrievalQA.from_chain_type(
208
+ llm=current_llm,
209
+ chain_type="stuff",
210
+ retriever=retriever,
211
+ return_source_documents=True,
212
+ )
213
+
214
+ result = qa_chain.invoke({"query": f"{message}.\n You must answer it in {self.language}. Remember not to mention anything that is not in the text. Do not extend information that is not provided in the text. "})
215
+
216
+ unique_page_labels = {doc.metadata['page_label'] for doc in result["source_documents"]}
217
+
218
+ page_labels_text = " & ".join([f"Page: {page}" for page in sorted(unique_page_labels)])
219
+
220
+ return result["result"] + "\n\nSources: " + page_labels_text
221
+
222
+
223
+ def summarizer_by_k_top_n(self, ai_model, type_model, api_key, project_id_watsonx, k, summary_prompt, just_get_documments=False):
224
+ if not self.vectorstore:
225
+ return TRANSLATIONS[self.language]["load_pdf_first"]
226
+
227
+ current_llm, _ = self.set_llm(ai_model, type_model, api_key, project_id_watsonx)
228
+ # Get all documents from the vectorstore
229
+ retriever = self.vectorstore.as_retriever(search_kwargs={"k": k})
230
+ documents = retriever.invoke('Summary of the document and key points')
231
+
232
+ if just_get_documments:
233
+ return "\n".join([doc.page_content for doc in documents])
234
+
235
+ summary_chain = summary_prompt | current_llm
236
+ final_summary = summary_chain.invoke({"texts": "\n".join([doc.page_content for doc in documents]), "language": self.language})
237
+ return final_summary
238
+
239
+ # Get the top k documents by score
240
+ def get_summary(self, ai_model, type_model, api_key, project_id_watsonx, just_get_documments=False, k=10):
241
+
242
+ final_summary_prompt = PromptTemplate(
243
+ input_variables=["texts", "language"],
244
+ template="""
245
+ Combine the following texts into a cohesive and structured final summary:
246
+ ------------
247
+ {texts}
248
+ ------------
249
+ The final summary should be between 2 and 4 paragraphs.
250
+ Preserve the original meaning without adding external information or interpretations.
251
+ Ensure clarity, logical flow, and coherence between the combined points.
252
+ The summary must be in {language}.
253
+ """
254
+ )
255
+
256
+ return self.summarizer_by_k_top_n(ai_model, type_model, api_key, project_id_watsonx, k, final_summary_prompt, just_get_documments)
257
+
258
+
259
+
260
+ def get_specialist_opinion(self, ai_model, type_model, api_key, project_id_watsonx, specialist_prompt):
261
+ questions_prompt = PromptTemplate(
262
+ input_variables=["text", "specialist_prompt", "language"],
263
+ template="""
264
+ * Act as a specialist based on the following instructions and behaviour that you will follow:
265
+ ------------
266
+ {specialist_prompt}
267
+ ------------
268
+ * Based on your role as specialist, create some different sintetized and concise aspects to ask to the knowledge base of the document about the following text:
269
+ ------------
270
+ {text}
271
+ ------------
272
+ * The key aspects and questions must be provided in JSON format with the following structure:
273
+ {{
274
+ "aspects": [
275
+ "Aspect 1",
276
+ "Aspect 2",
277
+ "Aspect 3",
278
+ "Aspect 4",
279
+ "Aspect 5",
280
+ "Aspect 6",
281
+ "Aspect 7",
282
+ "Aspect 8",
283
+ "Aspect 9",
284
+ "Aspect 10",
285
+ ]
286
+ }}
287
+ ------------
288
+ *Example of valid output:
289
+ {{
290
+ "aspects": [
291
+ "Finished date of the project",
292
+ "Payment of the project",
293
+ "Project extension"
294
+ ]
295
+ }}
296
+ ------------
297
+ * The aspects must be redacted in the language of {language}.
298
+ * The given structure must be followed strictly in front of the keys, just use the list of aspects, do not add any other key.
299
+ * Generate until 10 different aspects.
300
+ ------------
301
+ Answer:
302
+ """
303
+ )
304
+ if not self.vectorstore:
305
+ return TRANSLATIONS[self.language]["load_pdf_first"]
306
+
307
+ current_llm, _ = self.set_llm(ai_model, type_model, api_key, project_id_watsonx)
308
+
309
+ summary_text = self.get_summary(ai_model, type_model, api_key, project_id_watsonx, True, 10)
310
+ questions_chain = questions_prompt | current_llm
311
+ questions = questions_chain.invoke({"text": summary_text, "specialist_prompt": specialist_prompt, "language": self.language})
312
+
313
+ print(questions)
314
+
315
+ #clean the questions variable, delete all the text before the json and after the json
316
+ questions = questions.split("{")[1]
317
+ questions = questions.split("}")[0]
318
+ questions = questions.strip()
319
+ print(questions)
320
+ questions = json.loads(questions)
321
+
322
+ print(questions)
323
+
324
+ if len(questions["aspects"]) > 15:
325
+ questions["aspects"] = questions["aspects"][:15]
326
+ else:
327
+ questions["aspects"] = questions["aspects"]
328
+
329
+ aspects_text = "\n".join([f"* {aspect}: {self.get_qa_response(aspect, [], ai_model, type_model, api_key, project_id_watsonx, 2)}" for aspect in questions["aspects"]])
330
+
331
+ return aspects_text
332
+
333
+
334
+ """ Actúa como un abogado altamente experimentado en derecho civil y contractual.
335
+
336
+ Examina si existen cláusulas abusivas, desproporcionadas o contrarias a la normativa vigente, y explícalas con claridad.
337
+ Basa tu análisis en principios relevantes del derecho civil y contractual.
338
+ Ofrece un argumento estructurado y recomendaciones prácticas.
339
+ Si hay múltiples interpretaciones posibles, preséntalas de manera objetiva.
340
+ Mantén un tono profesional, preciso y fundamentado.
341
+
342
+ Basado en lo que analices, proporciona una evaluación legal detallada """
343
+
344
+ """ Actúa como un asesor e ingeniero financiero experto en lectura de reportes y análisis de datos.
345
+
346
+ Basado en los datos y conclusiones del reporte, proporciona una evaluación financiera detallada y posibles escenarios tanto negativos como positivos que se puedan presentar.
347
+ Establece el riesgo que se corre en cada escenario, la probabilidad de ocurrencia de cada uno y la magnitud del impacto en el recurso.
348
+ Si hay múltiples interpretaciones posibles, preséntalas de manera objetiva.
349
+ Realiza una hipótesis que pronostique el futuro de la situación o recurso analizado, teniendo en cuenta los datos y conclusiones del reporte.
350
+ Presenta tus hipotesis en 3 aspectos, corto, mediano y largo plazo.
351
+ Mantén un tono profesional, preciso y fundamentado.
352
+
353
+ Basado en lo que analices, proporciona una evaluación en detalle sobre los activos, reportes y/o recursos que se analizaron"""
utils.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ AI_MODELS = {
2
+ "Huggingface / IBM granite granite 3.1 8b Instruct": "ibm-granite/granite-3.1-8b-instruct",
3
+ "Huggingface / Mistral Small 24B Instruct": "mistralai/Mistral-Small-24B-Instruct-2501",
4
+ "Huggingface / SmolLM2 1.7B Instruct": "HuggingFaceTB/SmolLM2-1.7B-Instruct",
5
+ "IBM Granite3.1 dense / Ollama local": "ollama",
6
+ "Open AI / GPT-4o-mini": "openai",
7
+ }
8
+
9
+ TRANSLATIONS = {
10
+ "Español": {
11
+ "title": "# 📚 Procesador de PDF con QA y Resumen",
12
+ "api_key_required": "Para usar este modelo, necesitas una clave de API.",
13
+ "model_type": "Tipo de modelo",
14
+ "api_key_placeholder": "Ingresa tu clave de API",
15
+ "project_id_placeholder": "Ingresa tu ID de proyecto",
16
+ "ai_model": "Modelo AI",
17
+ "upload_pdf": "Cargar PDF",
18
+ "upload_images": "Cargar imágenes",
19
+ "chunk_size": "Tamaño de chunk",
20
+ "chunk_overlap": "Superposición de chunk",
21
+ "process_btn": "Procesar",
22
+ "processing_status": "Estado del procesamiento",
23
+ "qa_tab": "Preguntas y Respuestas",
24
+ "summary_tab": "Resumen",
25
+ "chat_placeholder": "Haz una pregunta sobre el documento...",
26
+ "chat_title": "Pregunta al documento",
27
+ "chat_btn": "Preguntar",
28
+ "generate_summary": "Generar Resumen",
29
+ "summary_label": "Resumen del documento",
30
+ "pdf_processed": "PDF procesado y almacenado correctamente",
31
+ "load_pdf_first": "Por favor, carga un PDF primero.",
32
+ "map_prompt": """Escribe un resumen conciso del siguiente texto:
33
+ "{text}"
34
+ RESUMEN CONCISO:""",
35
+ "combine_prompt": """Escribe un resumen detallado basado en los siguientes resúmenes de diferentes secciones del texto:
36
+ "{text}"
37
+ RESUMEN DETALLADO:""",
38
+ "mini_summary_title": "Resúmenes de cada fragmento",
39
+ "mini_analysis_title": "Análisis de cada fragmento",
40
+ "specialist_tab": "Asesor a tu medida",
41
+ "specialist_title": "Asesor a tu medida",
42
+ "specialist_label": "Establece el comportamiento y rol de tu asesor. Ej: Eres un especialista de finanzas que ayuda a interpretar los datos de un reporte financiero. A partir del documento y tu basta experiencia cuéntame que oportunidades y riesgos ves al invertir en lo que te proponen.",
43
+ "specialist_output": "Respuesta de tu asesor",
44
+ "specialist_btn": "Generar Respuesta"
45
+ },
46
+ "English": {
47
+ "title": "# 📚 PDF Processor with QA and Summary",
48
+ "api_key_required": "To use this model, you need an API key.",
49
+ "model_type": "Model type",
50
+ "api_key_placeholder": "Enter your API key",
51
+ "project_id_placeholder": "Enter your project ID",
52
+ "ai_model": "AI Model",
53
+ "upload_pdf": "Upload PDF",
54
+ "upload_images": "Upload Images",
55
+ "chunk_size": "Chunk size",
56
+ "chunk_overlap": "Chunk overlap",
57
+ "process_btn": "Process",
58
+ "processing_status": "Processing status",
59
+ "qa_tab": "Questions and Answers",
60
+ "summary_tab": "Summary",
61
+ "chat_placeholder": "Ask a question about the document...",
62
+ "chat_title": "Question to document",
63
+ "chat_btn": "Ask",
64
+ "generate_summary": "Generate Summary",
65
+ "summary_label": "Document summary",
66
+ "pdf_processed": "PDF processed and stored successfully",
67
+ "load_pdf_first": "Please load a PDF first.",
68
+ "map_prompt": """Write a concise summary of the following text:
69
+ "{text}"
70
+ CONCISE SUMMARY:""",
71
+ "combine_prompt": """Write a detailed summary based on the following summaries from different sections of the text:
72
+ "{text}"
73
+ DETAILED SUMMARY:""",
74
+ "mini_summary_title": "Summaries of each fragment",
75
+ "mini_analysis_title": "Analysis of each fragment",
76
+ "specialist_tab": "Customized Advisor",
77
+ "specialist_title": "Customized Advisor",
78
+ "specialist_label": "Set the behavior and role of your advisor. Example: You are a financial expert who helps interpret the data of a financial report. Based on the document and your extensive experience, tell me what opportunities and risks you see in what they propose.",
79
+ "specialist_output": "Answer of your advisor",
80
+ "specialist_btn": "Generate Answer"
81
+ },
82
+ "Deutsch": {
83
+ "title": "# 📚 PDF-Prozessor mit Q&A und Zusammenfassung",
84
+ "model_type": "Modelltyp",
85
+ "api_key_required": "Um dieses Modell zu verwenden, benötigen Sie einen API-Schlüssel.",
86
+ "api_key_placeholder": "API-Schlüssel eingeben",
87
+ "project_id_placeholder": "Projekt-ID eingeben",
88
+ "ai_model": "AI-Modell",
89
+ "upload_pdf": "PDF hochladen",
90
+ "upload_images": "Bilder hochladen",
91
+ "chunk_size": "Chunk-Größe",
92
+ "chunk_overlap": "Chunk-Überlappung",
93
+ "process_btn": "PDF verarbe",
94
+ "processing_status": "Verarbeitungsstatus",
95
+ "qa_tab": "Fragen und Antworten",
96
+ "summary_tab": "Zusammenfassung",
97
+ "chat_placeholder": "Stellen Sie eine Frage zum Dokument...",
98
+ "chat_title": "Frage zum Dokument",
99
+ "chat_btn": "Fragen",
100
+ "generate_summary": "Zusammenfassung generieren",
101
+ "summary_label": "Dokumentzusammenfassung",
102
+ "pdf_processed": "PDF erfolgreich verarbeitet und gespeichert",
103
+ "load_pdf_first": "Bitte laden Sie zuerst ein PDF hoch.",
104
+ "map_prompt": """Schreiben Sie eine kurze Zusammenfassung des folgenden Textes:
105
+ "{text}"
106
+ KURZE ZUSAMMENFASSUNG:""",
107
+ "combine_prompt": """Schreiben Sie eine detaillierte Zusammenfassung basierend auf den folgenden Zusammenfassungen verschiedener Textabschnitte:
108
+ "{text}"
109
+ DETAILLIERTE ZUSAMMENFASSUNG:""",
110
+ "mini_summary_title": "Zusammenfassungen von jedem Fragment",
111
+ "mini_analysis_title": "Analyse von jedem Fragment",
112
+ "specialist_tab": "Anpassbarer Berater",
113
+ "specialist_title": "Anpassbarer Berater",
114
+ "specialist_label": "Setzen Sie das Verhalten und die Rolle Ihres Beraters fest. Beispiel: Sie sind ein Finanzexperte, der bei der Interpretation von Finanzdaten aus einem Bericht hilft. Basierend auf dem Dokument und Ihrer umfassenden Erfahrung, erzählen Sie mir, was Sie in dem sehen, was sie Ihnen vorschlagen.",
115
+ "specialist_output": "Antwort Ihres Beraters",
116
+ "specialist_btn": "Antwort generieren"
117
+ },
118
+ "Français": {
119
+ "title": "# 📚 Processeur PDF avec QR et Résumé",
120
+ "model_type": "Type de modèle",
121
+ "api_key_required": "Pour utiliser ce modèle, vous avez besoin d'une clé API.",
122
+ "api_key_placeholder": "Entrez votre clé API",
123
+ "project_id_placeholder": "Entrez votre ID de projet",
124
+ "ai_model": "Modèle AI",
125
+ "upload_pdf": "Charger PDF",
126
+ "upload_images": "Charger images",
127
+ "chunk_size": "Taille du chunk",
128
+ "chunk_overlap": "Chevauchement du chunk",
129
+ "process_btn": "Traiter le",
130
+ "processing_status": "État du traitement",
131
+ "qa_tab": "Questions et Réponses",
132
+ "summary_tab": "Résumé",
133
+ "chat_placeholder": "Posez une question sur le document...",
134
+ "chat_title": "Question au document",
135
+ "chat_btn": "Poser une question",
136
+ "generate_summary": "Générer le résumé",
137
+ "summary_label": "Résumé du document",
138
+ "pdf_processed": "PDF traité et enregistré avec succès",
139
+ "load_pdf_first": "Veuillez d'abord charger un PDF.",
140
+ "map_prompt": """Écrivez un résumé concis du texte suivant :
141
+ "{text}"
142
+ RÉSUMÉ CONCIS :""",
143
+ "combine_prompt": """Écrivez un résumé détaillé basé sur les résumés suivants de différentes sections du texte :
144
+ "{text}"
145
+ RÉSUMÉ DÉTAILLÉ :""",
146
+ "mini_summary_title": "Résumés de chaque fragment",
147
+ "mini_analysis_title": "Analyse de chaque fragment",
148
+ "specialist_tab": "Conseiller personnalisé",
149
+ "specialist_title": "Conseiller personnalisé",
150
+ "specialist_label": "Définissez le comportement et le rôle de votre conseiller. Exemple : Vous êtes un expert financier qui aide à interpréter les données d'un rapport financier. Basé sur le document et votre vaste expérience, partagez-moi ce que vous voyez dans ce qu'ils vous proposent.",
151
+ "specialist_output": "Réponse de votre conseiller",
152
+ "specialist_btn": "Générer la réponse"
153
+ },
154
+ "Português": {
155
+ "title": "# 📚 Processador de PDF com P&R e Resumo",
156
+ "model_type": "Tipo de modelo",
157
+ "api_key_required": "Para usar este modelo, necesitas una clave de API.",
158
+ "api_key_placeholder": "Digite sua chave API",
159
+ "project_id_placeholder": "Digite seu ID de projeto",
160
+ "ai_model": "Modelo AI",
161
+ "upload_pdf": "Carregar PDF",
162
+ "upload_images": "Carregar imagens",
163
+ "chunk_size": "Tamanho do chunk",
164
+ "chunk_overlap": "Sobreposição do chunk",
165
+ "process_btn": "Processar",
166
+ "processing_status": "Status do processamento",
167
+ "qa_tab": "Perguntas e Respostas",
168
+ "summary_tab": "Resumo",
169
+ "chat_placeholder": "Faça uma pergunta sobre o documento...",
170
+ "chat_title": "Pergunta ao documento",
171
+ "chat_btn": "Perguntar",
172
+ "generate_summary": "Gerar Resumo",
173
+ "summary_label": "Resumo do documento",
174
+ "pdf_processed": "PDF processado e armazenado com sucesso",
175
+ "load_pdf_first": "Por favor, carregue um PDF primeiro.",
176
+ "map_prompt": """Escreva um resumo conciso do seguinte texto:
177
+ "{text}"
178
+ RESUMO CONCISO:""",
179
+ "combine_prompt": """Escreva um resumo detalhado baseado nos seguintes resumos de diferentes seções do texto:
180
+ "{text}"
181
+ RESUMO DETALHADO:""",
182
+ "mini_summary_title": "Resúmenes de cada fragmento",
183
+ "mini_analysis_title": "Análisis de cada fragmento",
184
+ "specialist_tab": "Assistente Personalizado",
185
+ "specialist_title": "Assistente Personalizado",
186
+ "specialist_label": "Defina o comportamento e o papel do seu assistente. Exemplo: Você é um especialista em finanças que ajuda a interpretar os dados de um relatório financeiro. Com base no documento e em sua ampla experiência, compartilhe comigo o que você vê naquilo que eles lhe propõem.",
187
+ "specialist_output": "Resposta do seu assistente",
188
+ "specialist_btn": "Gerar Resposta"
189
+ }
190
+ }