shamik commited on
Commit
0c2d302
·
unverified ·
1 Parent(s): 3cd77a3

feat: adding rest of the project files.

Browse files
.gitignore ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Ruff stuff:
171
+ .ruff_cache/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
175
+
176
+ pages*/
177
+ *milvus*
178
+
179
+ .gradio
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12
pyproject.toml ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "insurance-assistants"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "shamik", email = "39588365+Shamik-07@users.noreply.github.com" }
8
+ ]
9
+ requires-python = ">=3.12"
10
+ dependencies = [
11
+ "beautifulsoup4>=4.13.4",
12
+ "colpali-engine>=0.3.10",
13
+ "crawlee[all]>=0.6.9",
14
+ "duckduckgo-search>=8.0.2",
15
+ "gradio-pdf>=0.0.22",
16
+ "huggingface-hub[hf-xet]>=0.32.0",
17
+ "llama-index>=0.12.37",
18
+ "llama-index-llms-huggingface-api>=0.4.3",
19
+ "llama-index-tools-playwright>=0.2.0",
20
+ "markdownify>=1.1.0",
21
+ "pdf2image>=1.17.0",
22
+ "pydantic>=2.11.5",
23
+ "pymilvus>=2.5.10",
24
+ "pyprojroot>=0.3.0",
25
+ "python-dotenv>=1.1.0",
26
+ "qwen-vl-utils>=0.0.11",
27
+ "requests>=2.32.3",
28
+ "smolagents[gradio]>=1.16.1",
29
+ "torchvision>=0.21.0",
30
+ "wikipedia-api>=0.8.1",
31
+ ]
32
+
33
+
34
+ [dependency-groups]
35
+ dev = [
36
+ "jupyter>=1.1.1",
37
+ "pyrefly>=0.16.2",
38
+ "ruff>=0.11.11",
39
+ ]
requirements.txt ADDED
@@ -0,0 +1,656 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv pip compile pyproject.toml -o requirements.txt
3
+ accelerate==1.7.0
4
+ # via peft
5
+ aiofiles==24.1.0
6
+ # via gradio
7
+ aiohappyeyeballs==2.6.1
8
+ # via aiohttp
9
+ aiohttp==3.12.4
10
+ # via llama-index-core
11
+ aiosignal==1.3.2
12
+ # via aiohttp
13
+ aiosqlite==0.21.0
14
+ # via llama-index-core
15
+ annotated-types==0.7.0
16
+ # via pydantic
17
+ anyio==4.9.0
18
+ # via
19
+ # gradio
20
+ # httpx
21
+ # openai
22
+ # starlette
23
+ apify-fingerprint-datapoints==0.0.2
24
+ # via crawlee
25
+ arrow==1.3.0
26
+ # via cookiecutter
27
+ attrs==25.3.0
28
+ # via aiohttp
29
+ av==14.4.0
30
+ # via qwen-vl-utils
31
+ banks==2.1.2
32
+ # via llama-index-core
33
+ beautifulsoup4==4.13.4
34
+ # via
35
+ # insurance-assistants (pyproject.toml)
36
+ # crawlee
37
+ # llama-index-readers-file
38
+ # llama-index-tools-playwright
39
+ # markdownify
40
+ binaryornot==0.4.4
41
+ # via cookiecutter
42
+ blessed==1.21.0
43
+ # via inquirer
44
+ brotli==1.1.0
45
+ # via httpx
46
+ browserforge==1.2.3
47
+ # via crawlee
48
+ cachetools==6.0.0
49
+ # via crawlee
50
+ certifi==2025.1.31
51
+ # via
52
+ # crawlee
53
+ # curl-cffi
54
+ # httpcore
55
+ # httpx
56
+ # llama-cloud
57
+ # requests
58
+ cffi==1.17.1
59
+ # via curl-cffi
60
+ chardet==5.2.0
61
+ # via binaryornot
62
+ charset-normalizer==3.4.2
63
+ # via requests
64
+ click==8.2.1
65
+ # via
66
+ # browserforge
67
+ # cookiecutter
68
+ # duckduckgo-search
69
+ # llama-cloud-services
70
+ # nltk
71
+ # typer
72
+ # uvicorn
73
+ colorama==0.4.6
74
+ # via
75
+ # crawlee
76
+ # griffe
77
+ colpali-engine==0.3.10
78
+ # via insurance-assistants (pyproject.toml)
79
+ cookiecutter==2.6.0
80
+ # via crawlee
81
+ crawlee==0.6.9
82
+ # via insurance-assistants (pyproject.toml)
83
+ cssselect==1.3.0
84
+ # via parsel
85
+ curl-cffi==0.11.1
86
+ # via crawlee
87
+ dataclasses-json==0.6.7
88
+ # via llama-index-core
89
+ deprecated==1.2.18
90
+ # via
91
+ # banks
92
+ # llama-index-core
93
+ dirtyjson==1.0.8
94
+ # via llama-index-core
95
+ distro==1.9.0
96
+ # via openai
97
+ duckduckgo-search==8.0.2
98
+ # via insurance-assistants (pyproject.toml)
99
+ editor==1.6.6
100
+ # via inquirer
101
+ eval-type-backport==0.2.2
102
+ # via crawlee
103
+ fastapi==0.115.12
104
+ # via gradio
105
+ ffmpy==0.5.0
106
+ # via gradio
107
+ filelock==3.18.0
108
+ # via
109
+ # huggingface-hub
110
+ # tldextract
111
+ # torch
112
+ # transformers
113
+ filetype==1.2.0
114
+ # via llama-index-core
115
+ frozenlist==1.6.0
116
+ # via
117
+ # aiohttp
118
+ # aiosignal
119
+ fsspec==2025.5.1
120
+ # via
121
+ # gradio-client
122
+ # huggingface-hub
123
+ # llama-index-core
124
+ # torch
125
+ gradio==5.31.0
126
+ # via
127
+ # gradio-pdf
128
+ # smolagents
129
+ gradio-client==1.10.1
130
+ # via gradio
131
+ gradio-pdf==0.0.22
132
+ # via insurance-assistants (pyproject.toml)
133
+ greenlet==3.2.2
134
+ # via
135
+ # playwright
136
+ # sqlalchemy
137
+ griffe==1.7.3
138
+ # via banks
139
+ groovy==0.1.2
140
+ # via gradio
141
+ grpcio==1.67.1
142
+ # via pymilvus
143
+ h11==0.16.0
144
+ # via
145
+ # httpcore
146
+ # uvicorn
147
+ h2==4.2.0
148
+ # via httpx
149
+ hf-xet==1.1.2
150
+ # via huggingface-hub
151
+ hpack==4.1.0
152
+ # via h2
153
+ html5lib==1.1
154
+ # via crawlee
155
+ httpcore==1.0.9
156
+ # via httpx
157
+ httpx==0.28.1
158
+ # via
159
+ # crawlee
160
+ # gradio
161
+ # gradio-client
162
+ # llama-cloud
163
+ # llama-index-core
164
+ # openai
165
+ # safehttpx
166
+ huggingface-hub==0.32.3
167
+ # via
168
+ # insurance-assistants (pyproject.toml)
169
+ # accelerate
170
+ # gradio
171
+ # gradio-client
172
+ # llama-index-llms-huggingface-api
173
+ # peft
174
+ # smolagents
175
+ # tokenizers
176
+ # transformers
177
+ hyperframe==6.1.0
178
+ # via h2
179
+ idna==3.10
180
+ # via
181
+ # anyio
182
+ # httpx
183
+ # requests
184
+ # tldextract
185
+ # yarl
186
+ inquirer==3.4.0
187
+ # via crawlee
188
+ jaro-winkler==2.0.3
189
+ # via crawlee
190
+ jinja2==3.1.6
191
+ # via
192
+ # banks
193
+ # cookiecutter
194
+ # gradio
195
+ # smolagents
196
+ # torch
197
+ jiter==0.10.0
198
+ # via openai
199
+ jmespath==1.0.1
200
+ # via parsel
201
+ joblib==1.5.1
202
+ # via
203
+ # nltk
204
+ # scikit-learn
205
+ llama-cloud==0.1.23
206
+ # via
207
+ # llama-cloud-services
208
+ # llama-index-indices-managed-llama-cloud
209
+ llama-cloud-services==0.6.25
210
+ # via llama-parse
211
+ llama-index==0.12.38
212
+ # via insurance-assistants (pyproject.toml)
213
+ llama-index-agent-openai==0.4.8
214
+ # via
215
+ # llama-index
216
+ # llama-index-program-openai
217
+ llama-index-cli==0.4.1
218
+ # via llama-index
219
+ llama-index-core==0.12.38
220
+ # via
221
+ # llama-cloud-services
222
+ # llama-index
223
+ # llama-index-agent-openai
224
+ # llama-index-cli
225
+ # llama-index-embeddings-openai
226
+ # llama-index-indices-managed-llama-cloud
227
+ # llama-index-llms-huggingface-api
228
+ # llama-index-llms-openai
229
+ # llama-index-multi-modal-llms-openai
230
+ # llama-index-program-openai
231
+ # llama-index-question-gen-openai
232
+ # llama-index-readers-file
233
+ # llama-index-readers-llama-parse
234
+ # llama-index-tools-playwright
235
+ llama-index-embeddings-openai==0.3.1
236
+ # via
237
+ # llama-index
238
+ # llama-index-cli
239
+ llama-index-indices-managed-llama-cloud==0.7.1
240
+ # via llama-index
241
+ llama-index-llms-huggingface-api==0.5.0
242
+ # via insurance-assistants (pyproject.toml)
243
+ llama-index-llms-openai==0.3.44
244
+ # via
245
+ # llama-index
246
+ # llama-index-agent-openai
247
+ # llama-index-cli
248
+ # llama-index-multi-modal-llms-openai
249
+ # llama-index-program-openai
250
+ # llama-index-question-gen-openai
251
+ llama-index-multi-modal-llms-openai==0.4.3
252
+ # via llama-index
253
+ llama-index-program-openai==0.3.1
254
+ # via
255
+ # llama-index
256
+ # llama-index-question-gen-openai
257
+ llama-index-question-gen-openai==0.3.0
258
+ # via llama-index
259
+ llama-index-readers-file==0.4.8
260
+ # via llama-index
261
+ llama-index-readers-llama-parse==0.4.0
262
+ # via llama-index
263
+ llama-index-tools-playwright==0.2.0
264
+ # via insurance-assistants (pyproject.toml)
265
+ llama-parse==0.6.25
266
+ # via llama-index-readers-llama-parse
267
+ lxml==5.4.0
268
+ # via
269
+ # beautifulsoup4
270
+ # duckduckgo-search
271
+ # parsel
272
+ markdown-it-py==3.0.0
273
+ # via rich
274
+ markdownify==1.1.0
275
+ # via insurance-assistants (pyproject.toml)
276
+ markupsafe==3.0.2
277
+ # via
278
+ # gradio
279
+ # jinja2
280
+ marshmallow==3.26.1
281
+ # via dataclasses-json
282
+ mdurl==0.1.2
283
+ # via markdown-it-py
284
+ milvus-lite==2.4.12
285
+ # via pymilvus
286
+ more-itertools==10.7.0
287
+ # via crawlee
288
+ mpmath==1.3.0
289
+ # via sympy
290
+ multidict==6.4.4
291
+ # via
292
+ # aiohttp
293
+ # yarl
294
+ mypy-extensions==1.1.0
295
+ # via typing-inspect
296
+ nest-asyncio==1.6.0
297
+ # via llama-index-core
298
+ networkx==3.5
299
+ # via
300
+ # llama-index-core
301
+ # torch
302
+ nltk==3.9.1
303
+ # via
304
+ # llama-index
305
+ # llama-index-core
306
+ numpy==2.2.6
307
+ # via
308
+ # accelerate
309
+ # colpali-engine
310
+ # gradio
311
+ # llama-index-core
312
+ # pandas
313
+ # peft
314
+ # scikit-learn
315
+ # scipy
316
+ # torchvision
317
+ # transformers
318
+ nvidia-cublas-cu12==12.4.5.8
319
+ # via
320
+ # nvidia-cudnn-cu12
321
+ # nvidia-cusolver-cu12
322
+ # torch
323
+ nvidia-cuda-cupti-cu12==12.4.127
324
+ # via torch
325
+ nvidia-cuda-nvrtc-cu12==12.4.127
326
+ # via torch
327
+ nvidia-cuda-runtime-cu12==12.4.127
328
+ # via torch
329
+ nvidia-cudnn-cu12==9.1.0.70
330
+ # via torch
331
+ nvidia-cufft-cu12==11.2.1.3
332
+ # via torch
333
+ nvidia-curand-cu12==10.3.5.147
334
+ # via torch
335
+ nvidia-cusolver-cu12==11.6.1.9
336
+ # via torch
337
+ nvidia-cusparse-cu12==12.3.1.170
338
+ # via
339
+ # nvidia-cusolver-cu12
340
+ # torch
341
+ nvidia-cusparselt-cu12==0.6.2
342
+ # via torch
343
+ nvidia-nccl-cu12==2.21.5
344
+ # via torch
345
+ nvidia-nvjitlink-cu12==12.4.127
346
+ # via
347
+ # nvidia-cusolver-cu12
348
+ # nvidia-cusparse-cu12
349
+ # torch
350
+ nvidia-nvtx-cu12==12.4.127
351
+ # via torch
352
+ openai==1.82.1
353
+ # via
354
+ # llama-index-agent-openai
355
+ # llama-index-embeddings-openai
356
+ # llama-index-llms-openai
357
+ orjson==3.10.18
358
+ # via gradio
359
+ packaging==25.0
360
+ # via
361
+ # accelerate
362
+ # gradio
363
+ # gradio-client
364
+ # huggingface-hub
365
+ # marshmallow
366
+ # parsel
367
+ # peft
368
+ # qwen-vl-utils
369
+ # transformers
370
+ pandas==2.2.3
371
+ # via
372
+ # gradio
373
+ # llama-index-readers-file
374
+ # pymilvus
375
+ parsel==1.10.0
376
+ # via crawlee
377
+ pdf2image==1.17.0
378
+ # via insurance-assistants (pyproject.toml)
379
+ peft==0.15.2
380
+ # via colpali-engine
381
+ pillow==11.2.1
382
+ # via
383
+ # colpali-engine
384
+ # gradio
385
+ # llama-index-core
386
+ # pdf2image
387
+ # qwen-vl-utils
388
+ # smolagents
389
+ # torchvision
390
+ platformdirs==4.3.8
391
+ # via
392
+ # banks
393
+ # llama-cloud-services
394
+ playwright==1.52.0
395
+ # via
396
+ # crawlee
397
+ # llama-index-tools-playwright
398
+ primp==0.15.0
399
+ # via duckduckgo-search
400
+ propcache==0.3.1
401
+ # via
402
+ # aiohttp
403
+ # yarl
404
+ protego==0.4.0
405
+ # via crawlee
406
+ protobuf==6.31.1
407
+ # via pymilvus
408
+ psutil==7.0.0
409
+ # via
410
+ # accelerate
411
+ # crawlee
412
+ # peft
413
+ pycparser==2.22
414
+ # via cffi
415
+ pydantic==2.11.5
416
+ # via
417
+ # insurance-assistants (pyproject.toml)
418
+ # banks
419
+ # crawlee
420
+ # fastapi
421
+ # gradio
422
+ # llama-cloud
423
+ # llama-cloud-services
424
+ # llama-index-core
425
+ # openai
426
+ # pydantic-settings
427
+ pydantic-core==2.33.2
428
+ # via pydantic
429
+ pydantic-settings==2.6.1
430
+ # via crawlee
431
+ pydub==0.25.1
432
+ # via gradio
433
+ pyee==13.0.0
434
+ # via
435
+ # crawlee
436
+ # playwright
437
+ pygments==2.19.1
438
+ # via rich
439
+ pymilvus==2.5.10
440
+ # via insurance-assistants (pyproject.toml)
441
+ pypdf==5.5.0
442
+ # via llama-index-readers-file
443
+ pyprojroot==0.3.0
444
+ # via insurance-assistants (pyproject.toml)
445
+ python-dateutil==2.9.0.post0
446
+ # via
447
+ # arrow
448
+ # pandas
449
+ python-dotenv==1.1.0
450
+ # via
451
+ # insurance-assistants (pyproject.toml)
452
+ # llama-cloud-services
453
+ # pydantic-settings
454
+ # pymilvus
455
+ # smolagents
456
+ python-multipart==0.0.20
457
+ # via gradio
458
+ python-slugify==8.0.4
459
+ # via cookiecutter
460
+ pytz==2025.2
461
+ # via pandas
462
+ pyyaml==6.0.2
463
+ # via
464
+ # accelerate
465
+ # cookiecutter
466
+ # gradio
467
+ # huggingface-hub
468
+ # llama-index-core
469
+ # peft
470
+ # transformers
471
+ qwen-vl-utils==0.0.11
472
+ # via insurance-assistants (pyproject.toml)
473
+ readchar==4.2.1
474
+ # via inquirer
475
+ regex==2024.11.6
476
+ # via
477
+ # nltk
478
+ # tiktoken
479
+ # transformers
480
+ requests==2.32.3
481
+ # via
482
+ # insurance-assistants (pyproject.toml)
483
+ # colpali-engine
484
+ # cookiecutter
485
+ # huggingface-hub
486
+ # llama-index-core
487
+ # qwen-vl-utils
488
+ # requests-file
489
+ # smolagents
490
+ # tiktoken
491
+ # tldextract
492
+ # transformers
493
+ # wikipedia-api
494
+ requests-file==2.1.0
495
+ # via tldextract
496
+ rich==14.0.0
497
+ # via
498
+ # cookiecutter
499
+ # smolagents
500
+ # typer
501
+ ruff==0.11.12
502
+ # via gradio
503
+ runs==1.2.2
504
+ # via editor
505
+ safehttpx==0.1.6
506
+ # via gradio
507
+ safetensors==0.5.3
508
+ # via
509
+ # accelerate
510
+ # peft
511
+ # transformers
512
+ scikit-learn==1.6.1
513
+ # via crawlee
514
+ scipy==1.15.3
515
+ # via
516
+ # colpali-engine
517
+ # scikit-learn
518
+ semantic-version==2.10.0
519
+ # via gradio
520
+ setuptools==80.9.0
521
+ # via
522
+ # pymilvus
523
+ # torch
524
+ shellingham==1.5.4
525
+ # via typer
526
+ six==1.17.0
527
+ # via
528
+ # html5lib
529
+ # markdownify
530
+ # python-dateutil
531
+ smolagents==1.17.0
532
+ # via insurance-assistants (pyproject.toml)
533
+ sniffio==1.3.1
534
+ # via
535
+ # anyio
536
+ # openai
537
+ sortedcollections==2.1.0
538
+ # via crawlee
539
+ sortedcontainers==2.4.0
540
+ # via
541
+ # crawlee
542
+ # sortedcollections
543
+ soupsieve==2.7
544
+ # via beautifulsoup4
545
+ sqlalchemy==2.0.41
546
+ # via llama-index-core
547
+ starlette==0.46.2
548
+ # via
549
+ # fastapi
550
+ # gradio
551
+ striprtf==0.0.26
552
+ # via llama-index-readers-file
553
+ sympy==1.13.1
554
+ # via torch
555
+ tenacity==9.1.2
556
+ # via llama-index-core
557
+ text-unidecode==1.3
558
+ # via python-slugify
559
+ threadpoolctl==3.6.0
560
+ # via scikit-learn
561
+ tiktoken==0.9.0
562
+ # via llama-index-core
563
+ tldextract==5.3.0
564
+ # via crawlee
565
+ tokenizers==0.21.1
566
+ # via transformers
567
+ tomlkit==0.13.2
568
+ # via gradio
569
+ torch==2.6.0
570
+ # via
571
+ # accelerate
572
+ # colpali-engine
573
+ # peft
574
+ # torchvision
575
+ torchvision==0.21.0
576
+ # via insurance-assistants (pyproject.toml)
577
+ tqdm==4.67.1
578
+ # via
579
+ # huggingface-hub
580
+ # llama-index-core
581
+ # milvus-lite
582
+ # nltk
583
+ # openai
584
+ # peft
585
+ # transformers
586
+ transformers==4.51.3
587
+ # via
588
+ # colpali-engine
589
+ # peft
590
+ triton==3.2.0
591
+ # via torch
592
+ typer==0.16.0
593
+ # via
594
+ # crawlee
595
+ # gradio
596
+ types-python-dateutil==2.9.0.20250516
597
+ # via arrow
598
+ typing-extensions==4.13.2
599
+ # via
600
+ # aiosqlite
601
+ # anyio
602
+ # beautifulsoup4
603
+ # crawlee
604
+ # fastapi
605
+ # gradio
606
+ # gradio-client
607
+ # huggingface-hub
608
+ # llama-index-core
609
+ # openai
610
+ # pydantic
611
+ # pydantic-core
612
+ # pyee
613
+ # pyprojroot
614
+ # sqlalchemy
615
+ # torch
616
+ # typer
617
+ # typing-inspect
618
+ # typing-inspection
619
+ typing-inspect==0.9.0
620
+ # via
621
+ # dataclasses-json
622
+ # llama-index-core
623
+ typing-inspection==0.4.1
624
+ # via pydantic
625
+ tzdata==2025.2
626
+ # via pandas
627
+ ujson==5.10.0
628
+ # via pymilvus
629
+ urllib3==2.4.0
630
+ # via requests
631
+ uvicorn==0.34.2
632
+ # via gradio
633
+ w3lib==2.3.1
634
+ # via parsel
635
+ wcwidth==0.2.13
636
+ # via blessed
637
+ webencodings==0.5.1
638
+ # via html5lib
639
+ websockets==15.0.1
640
+ # via gradio-client
641
+ wikipedia-api==0.8.1
642
+ # via insurance-assistants (pyproject.toml)
643
+ wrapt==1.17.2
644
+ # via
645
+ # deprecated
646
+ # llama-index-core
647
+ xmod==1.8.1
648
+ # via
649
+ # editor
650
+ # runs
651
+ yarl==1.20.0
652
+ # via
653
+ # aiohttp
654
+ # crawlee
655
+ zstandard==0.23.0
656
+ # via httpx
src/insurance_assistants/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ def hello() -> str:
2
+ return "Hello from insurance-assistants!"
src/insurance_assistants/agents.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+
3
+ from dotenv import find_dotenv, load_dotenv
4
+ from huggingface_hub import InferenceClient
5
+ from smolagents import (
6
+ CodeAgent,
7
+ DuckDuckGoSearchTool,
8
+ FinalAnswerTool,
9
+ InferenceClientModel,
10
+ PythonInterpreterTool,
11
+ Tool,
12
+ ToolCallingAgent,
13
+ VisitWebpageTool,
14
+ WikipediaSearchTool,
15
+ )
16
+
17
+ from src.insurance_assistants.complex_rag import RAG
18
+ from src.insurance_assistants.consts import PROMPT_PREFIX
19
+
20
+ _ = load_dotenv(dotenv_path=find_dotenv())
21
+ rag_app = RAG()
22
+ # FIXME Comment the following if you want to reprocess everything
23
+ rag_app.vectordb_id = "policy_wordings"
24
+
25
+
26
+ class InsuranceInfoRetriever(Tool):
27
+ name = "InsuranceInfoRetriever"
28
+ description = "Retrieves information from insurance documents."
29
+ inputs = {
30
+ "query": {"type": "string", "description": "The query to search for."},
31
+ }
32
+ output_type = "string"
33
+
34
+ def forward(self, query: str) -> str:
35
+ client = InferenceClient(
36
+ provider="hyperbolic",
37
+ bill_to="VitalNest",
38
+ )
39
+ results = rag_app.search_documents(query)
40
+ img_paths = [Path(res[0]) for res in results]
41
+
42
+ grouped_images = [rag_app.encode_image_to_base64(pth) for pth in img_paths]
43
+ chat_template = [
44
+ {
45
+ "role": "system",
46
+ "content": """You find answers from the relevant documents. Answer only
47
+ from these documents. If answer isn't available return 'Question cannot be answered based
48
+ on the documents provided.' """,
49
+ },
50
+ {
51
+ "role": "user",
52
+ "content": [
53
+ {
54
+ "type": "image_url",
55
+ "image_url": {"url": f"data:image/jpeg;base64,{image}"},
56
+ }
57
+ for image in grouped_images
58
+ ]
59
+ + [{"type": "text", "text": query}],
60
+ },
61
+ ]
62
+ completion = client.chat.completions.create(
63
+ model="Qwen/Qwen2.5-VL-72B-Instruct",
64
+ messages=chat_template,
65
+ temperature=0.1,
66
+ max_tokens=10_000,
67
+ )
68
+ answer = completion.choices[0].message.content
69
+ if answer:
70
+ answer += f"The information was retrived from the following documents: {img_paths}"
71
+ return answer if answer else ""
72
+
73
+
74
+ insurance_agent = CodeAgent(
75
+ tools=[InsuranceInfoRetriever(), FinalAnswerTool()],
76
+ model=InferenceClientModel(bill_to="VitalNest", temperature=0.1),
77
+ additional_authorized_imports=["os", "requests", "bs4", "pil", "base64", "io"],
78
+ max_steps=1,
79
+ verbosity_level=-1,
80
+ name="insurance_agent",
81
+ description="You answer health insurance questions based on the InsuranceInfoRetriever "
82
+ "tool. All health insurance questions must be answered by you.",
83
+ )
84
+ websearch_agent = ToolCallingAgent(
85
+ model=InferenceClientModel(
86
+ model_id="Qwen/Qwen3-30B-A3B", bill_to="VitalNest", temperature=0.1
87
+ ),
88
+ tools=[
89
+ VisitWebpageTool(max_output_length=20_000),
90
+ DuckDuckGoSearchTool(max_results=5),
91
+ FinalAnswerTool(),
92
+ ],
93
+ max_steps=4,
94
+ verbosity_level=-1,
95
+ name="web_search_agent",
96
+ planning_interval=2,
97
+ description="Searches the web with a particular query.",
98
+ )
99
+
100
+ wikipedia_agent = ToolCallingAgent(
101
+ model=InferenceClientModel(
102
+ model_id="Qwen/Qwen3-30B-A3B", bill_to="VitalNest", temperature=0.1
103
+ ),
104
+ tools=[
105
+ WikipediaSearchTool(user_agent="WikiAssistant (merlin@example.com)"),
106
+ FinalAnswerTool(),
107
+ ],
108
+ max_steps=3,
109
+ verbosity_level=-1,
110
+ name="wikipedia_agent",
111
+ description="Searches Wikipedia for a topic.",
112
+ )
113
+
114
+ manager_agent = CodeAgent(
115
+ tools=[FinalAnswerTool(), PythonInterpreterTool()],
116
+ additional_authorized_imports=["os"],
117
+ model=InferenceClientModel(
118
+ model_id="Qwen/Qwen3-235B-A22B",
119
+ bill_to="VitalNest",
120
+ temperature=0.1,
121
+ ),
122
+ managed_agents=[websearch_agent, wikipedia_agent, insurance_agent],
123
+ max_steps=10,
124
+ planning_interval=2,
125
+ verbosity_level=-1,
126
+ add_base_tools=True,
127
+ name="Versatile_Multi_Agent",
128
+ description="Answer health insurance related questions from pre-defined set of "
129
+ "health insurance documents, search wikipedia and the web for general information.",
130
+ )
131
+ manager_agent.system_prompt = manager_agent.system_prompt + PROMPT_PREFIX
src/insurance_assistants/complex_rag.py ADDED
@@ -0,0 +1,600 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # %%
2
+ import base64
3
+ import concurrent.futures
4
+ import logging
5
+ import os
6
+ import shutil
7
+ from io import BytesIO
8
+ from pathlib import Path
9
+
10
+ import numpy as np
11
+ import torch
12
+ from colpali_engine.models import (
13
+ # ColPali,
14
+ # ColPaliProcessor,
15
+ ColQwen2_5,
16
+ ColQwen2_5_Processor,
17
+ )
18
+ from colpali_engine.utils.torch_utils import ListDataset, get_torch_device
19
+ from dotenv import find_dotenv, load_dotenv
20
+ from openai import OpenAI
21
+ from pdf2image import convert_from_path
22
+ from PIL import Image
23
+ from pymilvus import DataType, MilvusClient
24
+ from torch.utils.data import DataLoader
25
+ from tqdm import tqdm
26
+
27
+ from src.insurance_assistants.consts import PROJECT_ROOT_DIR, PROMPT
28
+
29
+ # Setup logger
30
+ logging.basicConfig(level=logging.INFO)
31
+ logger = logging.getLogger(__name__)
32
+
33
+ # %%
34
+ model_name = "vidore/colqwen2.5-v0.2"
35
+ # model_name = "vidore/colpali-v1.2"
36
+ device = get_torch_device()
37
+
38
+ model = ColQwen2_5.from_pretrained(
39
+ # model = ColPali.from_pretrained(
40
+ pretrained_model_name_or_path=model_name,
41
+ torch_dtype=torch.bfloat16,
42
+ device_map=device,
43
+ ).eval()
44
+
45
+ processor = ColQwen2_5_Processor.from_pretrained(
46
+ # processor = ColPaliProcessor.from_pretrained(
47
+ pretrained_model_name_or_path=model_name,
48
+ use_fast=True,
49
+ )
50
+ _ = load_dotenv(dotenv_path=find_dotenv(raise_error_if_not_found=True))
51
+ openai_client = OpenAI()
52
+
53
+
54
+ # %%
55
+ class MilvusManager:
56
+ def __init__(self, milvus_uri, collection_name, create_collection, dim=128):
57
+ """
58
+ Initializes the MilvusManager.
59
+
60
+ Args:
61
+ milvus_uri (str): URI for Milvus server.
62
+ collection_name (str): Name of the collection.
63
+ create_collection (bool): Whether to create a new collection.
64
+ dim (int, optional): Dimension of the vector. Defaults to 128.
65
+ """
66
+ self.client = MilvusClient(uri=milvus_uri)
67
+ self.collection_name = collection_name
68
+ if self.client.has_collection(collection_name=self.collection_name):
69
+ self.client.load_collection(collection_name)
70
+ self.dim = dim
71
+ self.max_doc_id = 0
72
+
73
+ if create_collection:
74
+ self.create_collection()
75
+ self.create_index()
76
+
77
+ def create_collection(self):
78
+ """
79
+ Creates a new collection in Milvus. Drops existing collection if present.
80
+ """
81
+ if self.client.has_collection(collection_name=self.collection_name):
82
+ self.client.drop_collection(collection_name=self.collection_name)
83
+ schema = self.client.create_schema(
84
+ auto_id=True,
85
+ enable_dynamic_fields=True,
86
+ )
87
+ schema.add_field(field_name="pk", datatype=DataType.INT64, is_primary=True)
88
+ schema.add_field(
89
+ field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=self.dim
90
+ )
91
+ schema.add_field(field_name="seq_id", datatype=DataType.INT16)
92
+ schema.add_field(field_name="doc_id", datatype=DataType.INT64)
93
+ schema.add_field(field_name="doc", datatype=DataType.VARCHAR, max_length=65535)
94
+
95
+ self.client.create_collection(
96
+ collection_name=self.collection_name, schema=schema
97
+ )
98
+
99
+ def create_index(self):
100
+ """
101
+ Creates a vector index for the collection in Milvus.
102
+ """
103
+ self.client.release_collection(collection_name=self.collection_name)
104
+ self.client.drop_index(
105
+ collection_name=self.collection_name, index_name="vector"
106
+ )
107
+ index_params = self.client.prepare_index_params()
108
+ index_params.add_index(
109
+ field_name="vector",
110
+ index_name="vector_index",
111
+ index_type="FLAT",
112
+ metric_type="IP",
113
+ params={
114
+ "M": 16,
115
+ "efConstruction": 500,
116
+ },
117
+ )
118
+
119
+ self.client.create_index(
120
+ collection_name=self.collection_name, index_params=index_params, sync=True
121
+ )
122
+
123
+ def create_scalar_index(self):
124
+ """
125
+ Creates a scalar index for the doc_id field in Milvus.
126
+ """
127
+ self.client.release_collection(collection_name=self.collection_name)
128
+
129
+ index_params = self.client.prepare_index_params()
130
+ index_params.add_index(
131
+ field_name="doc_id",
132
+ index_name="int32_index",
133
+ index_type="INVERTED",
134
+ )
135
+
136
+ self.client.create_index(
137
+ collection_name=self.collection_name, index_params=index_params, sync=True
138
+ )
139
+
140
+ def search(self, data, topk):
141
+ """
142
+ Searches for the top-k most similar documents in Milvus.
143
+
144
+ Args:
145
+ data (np.ndarray): Query vector.
146
+ topk (int): Number of top results to return.
147
+
148
+ Returns:
149
+ list: List of (score, doc_id) tuples.
150
+ """
151
+ search_params = {"metric_type": "IP", "params": {}}
152
+ results = self.client.search(
153
+ self.collection_name,
154
+ data,
155
+ limit=50,
156
+ output_fields=["vector", "seq_id", "doc_id"],
157
+ search_params=search_params,
158
+ )
159
+ doc_ids = set()
160
+ for r_id in range(len(results)):
161
+ for r in range(len(results[r_id])):
162
+ doc_ids.add(results[r_id][r]["entity"]["doc_id"])
163
+
164
+ scores = []
165
+
166
+ def rerank_single_doc(doc_id, data, client, collection_name):
167
+ doc_colqwen_vecs = client.query(
168
+ collection_name=collection_name,
169
+ filter=f"doc_id in [{doc_id}, {doc_id + 1}]",
170
+ output_fields=["seq_id", "vector", "doc"],
171
+ limit=1000,
172
+ )
173
+ doc_vecs = np.vstack(
174
+ [doc_colqwen_vecs[i]["vector"] for i in range(len(doc_colqwen_vecs))]
175
+ )
176
+ score = np.dot(data, doc_vecs.T).max(1).sum()
177
+ return (score, doc_id)
178
+
179
+ with concurrent.futures.ThreadPoolExecutor(max_workers=300) as executor:
180
+ futures = {
181
+ executor.submit(
182
+ rerank_single_doc, doc_id, data, self.client, self.collection_name
183
+ ): doc_id
184
+ for doc_id in doc_ids
185
+ }
186
+ for future in concurrent.futures.as_completed(futures):
187
+ score, doc_id = future.result()
188
+ scores.append((score, doc_id))
189
+
190
+ scores.sort(key=lambda x: x[0], reverse=True)
191
+ if len(scores) >= topk:
192
+ return scores[:topk]
193
+ else:
194
+ return scores
195
+
196
+ def insert(self, data):
197
+ """
198
+ Inserts a document's vectors and metadata into Milvus.
199
+
200
+ Args:
201
+ data (dict): Dictionary containing 'colqwen_vecs', 'doc_id', and 'filepath'.
202
+ """
203
+ colqwen_vecs = [vec for vec in data["colqwen_vecs"]]
204
+ seq_length = len(colqwen_vecs)
205
+ doc_ids = [data["doc_id"] for _ in range(seq_length)]
206
+ seq_ids = list(range(seq_length))
207
+ docs = [data["filepath"] for _ in range(seq_length)]
208
+ # docs = [""] * seq_length
209
+ # docs[0] = data["filepath"]
210
+
211
+ self.client.insert(
212
+ self.collection_name,
213
+ [
214
+ {
215
+ "vector": colqwen_vecs[i],
216
+ "seq_id": seq_ids[i],
217
+ "doc_id": doc_ids[i],
218
+ "doc": docs[i],
219
+ }
220
+ for i in range(seq_length)
221
+ ],
222
+ )
223
+
224
+ def get_images_as_doc(self, images_with_vectors: list):
225
+ """
226
+ Converts a list of image vectors and filepaths into Milvus insertable format.
227
+
228
+ Args:
229
+ images_with_vectors (list): List of dicts with 'colqwen_vecs' and 'filepath'.
230
+
231
+ Returns:
232
+ list: List of dicts ready for Milvus insertion.
233
+ """
234
+ images_data = []
235
+
236
+ for i in range(len(images_with_vectors)):
237
+ self.max_doc_id += 1
238
+ data = {
239
+ "colqwen_vecs": images_with_vectors[i]["colqwen_vecs"],
240
+ "doc_id": self.max_doc_id,
241
+ "filepath": images_with_vectors[i]["filepath"],
242
+ }
243
+ images_data.append(data)
244
+
245
+ return images_data
246
+
247
+ def insert_images_data(self, image_data):
248
+ """
249
+ Inserts multiple images' data into Milvus.
250
+
251
+ Args:
252
+ image_data (list): List of image data dicts.
253
+ """
254
+ data = self.get_images_as_doc(image_data)
255
+
256
+ for i in range(len(data)):
257
+ self.insert(data[i])
258
+
259
+
260
+ # %%
261
+ class VectorProcessor:
262
+ def __init__(
263
+ self,
264
+ id: str,
265
+ create_collection=True,
266
+ ):
267
+ """
268
+ Initializes the VectorProcessor with Milvus, Colqwen, and PDF managers.
269
+
270
+ Args:
271
+ id (str): Unique identifier for the session/user.
272
+ create_collection (bool, optional): Whether to create a new collection. Defaults to True.
273
+ """
274
+ # hashed_id = hashlib.md5(id.encode()).hexdigest()[:8]
275
+ # milvus_db_name = f"milvus_{hashed_id}.db"
276
+ milvus_db_name = (
277
+ PROJECT_ROOT_DIR / f"src/insurance_assistants/milvus_{id}.db"
278
+ ).as_posix()
279
+ self.milvus_manager = MilvusManager(milvus_db_name, f"{id}", create_collection)
280
+ self.colqwen_manager = ColqwenManager()
281
+ self.pdf_manager = PdfManager()
282
+
283
+ def index(
284
+ self,
285
+ pdf_path: str,
286
+ id: str,
287
+ max_pages: int,
288
+ ):
289
+ """
290
+ Indexes a PDF file by converting pages to images, embedding them, and storing in Milvus.
291
+
292
+ Args:
293
+ pdf_path (str): Path to the PDF file.
294
+ id (str): Unique identifier.
295
+ max_pages (int): Maximum number of pages to process.
296
+
297
+ Returns:
298
+ list: List of saved image paths.
299
+ """
300
+ logger.info(f"Indexing {pdf_path}, id: {id}, max_pages: {max_pages}")
301
+
302
+ image_paths = self.pdf_manager.save_images(id, pdf_path, max_pages)
303
+
304
+ logger.info(f"Saved {len(image_paths)} images")
305
+
306
+ colqwen_vecs = self.colqwen_manager.process_images(image_paths)
307
+
308
+ images_data = [
309
+ {"colqwen_vecs": colqwen_vecs[i], "filepath": image_paths[i]}
310
+ for i in range(len(image_paths))
311
+ ]
312
+
313
+ logger.info(f"Inserting {len(images_data)} images data to Milvus")
314
+
315
+ self.milvus_manager.insert_images_data(images_data)
316
+
317
+ logger.info("Indexing completed")
318
+
319
+ return image_paths
320
+
321
+ def search(self, search_queries: list[str]):
322
+ logger.info(f"Searching for {len(search_queries)} queries")
323
+
324
+ final_res = []
325
+
326
+ for query in search_queries:
327
+ logger.info(f"Searching for query: {query}")
328
+ query_vec = self.colqwen_manager.process_text([query])[0]
329
+ search_res = self.milvus_manager.search(query_vec, topk=4)
330
+ logger.info(f"Search result: {search_res} for query: {query}")
331
+ final_res.append(search_res)
332
+
333
+ return final_res
334
+
335
+
336
+ # %%
337
+ class PdfManager:
338
+ def __init__(self):
339
+ """
340
+ Initializes the PdfManager.
341
+ """
342
+ pass
343
+
344
+ def clear_and_recreate_dir(self, output_folder):
345
+ logger.info(f"Clearing output folder {output_folder}")
346
+
347
+ if os.path.exists(output_folder):
348
+ shutil.rmtree(output_folder)
349
+
350
+ os.makedirs(output_folder)
351
+
352
+ def save_images(
353
+ self, id, pdf_path, max_pages, pages: list[int] = None, output_folder=None
354
+ ) -> list[str]:
355
+ """
356
+ Saves images of PDF pages to disk.
357
+
358
+ Args:
359
+ id (str): Unique identifier.
360
+ pdf_path (str): Path to the PDF file.
361
+ max_pages (int): Maximum number of pages to save.
362
+ pages (list[int], optional): Specific pages to save. Defaults to None.
363
+
364
+ Returns:
365
+ list[str]: List of saved image file paths.
366
+ """
367
+ output_folder = (
368
+ Path(output_folder) if output_folder is not None else output_folder
369
+ )
370
+ if output_folder is None:
371
+ output_folder = PROJECT_ROOT_DIR / f"src/insurance_assistants/pages/{id}/"
372
+ if not output_folder.exists():
373
+ output_folder.mkdir(parents=True, exist_ok=True)
374
+ images = convert_from_path(pdf_path=pdf_path)
375
+
376
+ logger.info(
377
+ f"Saving images from {pdf_path} to {output_folder}. Max pages: {max_pages}"
378
+ )
379
+
380
+ # self.clear_and_recreate_dir(output_folder)
381
+
382
+ num_page_processed = 0
383
+
384
+ for i, image in enumerate(images):
385
+ if max_pages and num_page_processed >= max_pages:
386
+ break
387
+
388
+ if pages and i not in pages:
389
+ continue
390
+
391
+ full_save_path = output_folder / f"{id}_page_{i + 1}.png"
392
+
393
+ # logger.debug(f"Saving image to {full_save_path}")
394
+
395
+ image.save(fp=full_save_path, format="PNG")
396
+
397
+ num_page_processed += 1
398
+
399
+ return [
400
+ f"{output_folder}/{id}_page_{i + 1}.png" for i in range(num_page_processed)
401
+ ]
402
+
403
+
404
+ # %%
405
+ class ColqwenManager:
406
+ def get_images(self, paths: list[str]) -> list[Image.Image]:
407
+ """
408
+ Loads images from file paths.
409
+
410
+ Args:
411
+ paths (list[str]): List of image file paths.
412
+
413
+ Returns:
414
+ list[Image.Image]: List of PIL Image objects.
415
+ """
416
+ return [Image.open(path) for path in paths]
417
+
418
+ def process_images(self, image_paths: list[str], batch_size=5):
419
+ logger.info(f"Processing {len(image_paths)} image_paths")
420
+
421
+ images = self.get_images(image_paths)
422
+
423
+ dataloader = DataLoader(
424
+ dataset=ListDataset[str](images),
425
+ batch_size=batch_size,
426
+ shuffle=False,
427
+ collate_fn=lambda x: processor.process_images(x),
428
+ )
429
+
430
+ ds: list[torch.Tensor] = []
431
+ for batch_doc in tqdm(dataloader):
432
+ with torch.no_grad():
433
+ batch_doc = {k: v.to(model.device) for k, v in batch_doc.items()}
434
+ embeddings_doc = model(**batch_doc)
435
+ ds.extend(list(torch.unbind(embeddings_doc.to(device))))
436
+
437
+ ds_np = [d.float().cpu().numpy() for d in ds]
438
+
439
+ return ds_np
440
+
441
+ def process_text(self, texts: list[str]):
442
+ logger.info(f"Processing {len(texts)} texts")
443
+
444
+ dataloader = DataLoader(
445
+ dataset=ListDataset[str](texts),
446
+ batch_size=1,
447
+ shuffle=False,
448
+ collate_fn=lambda x: processor.process_queries(x),
449
+ )
450
+
451
+ qs: list[torch.Tensor] = []
452
+ for batch_query in dataloader:
453
+ with torch.no_grad():
454
+ batch_query = {k: v.to(model.device) for k, v in batch_query.items()}
455
+ embeddings_query = model(**batch_query)
456
+
457
+ qs.extend(list(torch.unbind(embeddings_query.to(device))))
458
+
459
+ qs_np = [q.float().cpu().numpy() for q in qs]
460
+
461
+ return qs_np
462
+
463
+
464
+ # %%
465
+ # def generate_uuid(state):
466
+ # """
467
+ # Generates or retrieves a UUID for the user session.
468
+
469
+ # Args:
470
+ # state (dict): State dictionary containing 'user_uuid'.
471
+
472
+ # Returns:
473
+ # str: UUID string.
474
+ # """
475
+ # # Check if UUID already exists in session state
476
+ # if state["user_uuid"] is None:
477
+ # # Generate a new UUID if not already set
478
+ # state["user_uuid"] = str(uuid.uuid4())
479
+
480
+ # return state["user_uuid"]
481
+
482
+
483
+ class RAG:
484
+ def __init__(self):
485
+ """
486
+ Initializes the RAG.
487
+ """
488
+ self.vectordb_id = None
489
+ self.img_path_dir = PROJECT_ROOT_DIR / "src/insurance_assistants/pages/"
490
+
491
+ def create_vector_db(
492
+ self,
493
+ vectordb_id="policy_wordings",
494
+ dir=PROJECT_ROOT_DIR / "data",
495
+ max_pages=200,
496
+ ):
497
+ """
498
+ Uploads a PDF file, converts it to images, and indexes it.
499
+
500
+ Args:
501
+ state (dict): State dictionary for user session.
502
+ file: Uploaded file object.
503
+ max_pages (int, optional): Maximum number of pages to process. Defaults to 100.
504
+
505
+ Returns:
506
+ str: Status message.
507
+ """
508
+
509
+ logger.info(f"Converting files in: {dir}.")
510
+
511
+ try:
512
+ for idx, f in enumerate((dir / "policy_wordings").iterdir()):
513
+ if idx == 0:
514
+ vectorprocessor = VectorProcessor(
515
+ id=vectordb_id, create_collection=True
516
+ )
517
+ self.vectordb_id = vectordb_id
518
+ _ = vectorprocessor.index(pdf_path=f, id=f.stem, max_pages=max_pages)
519
+ vectorprocessor.milvus_manager.client.close()
520
+ return f"✅ Created the vector_db: milvus_{vectordb_id} under `src` dir."
521
+ except Exception as err:
522
+ return f"❌ Error creating vector_db: {err}"
523
+
524
+ def search_documents(self, query):
525
+ if self.vectordb_id is None:
526
+ raise Exception(
527
+ "Create the vector db first by invoking `create_vector_db`."
528
+ )
529
+ try:
530
+ vectorprocessor = VectorProcessor(
531
+ id=self.vectordb_id, create_collection=False
532
+ )
533
+
534
+ search_results = vectorprocessor.search(search_queries=[query])[0]
535
+
536
+ check_res = vectorprocessor.milvus_manager.client.query(
537
+ collection_name=self.vectordb_id,
538
+ filter=f"doc_id in {[d[1] for d in search_results]}",
539
+ output_fields=["doc_id", "doc"],
540
+ )
541
+ vectorprocessor.milvus_manager.client.close()
542
+ img_path_doc_id = set((i["doc"], i["doc_id"]) for i in check_res)
543
+
544
+ logger.info("✅ Retrieved the images for answering query.")
545
+ return img_path_doc_id
546
+
547
+ except Exception as err:
548
+ return f"❌ Error during search: {err}"
549
+
550
+ def encode_image_to_base64(self, image_path):
551
+ """
552
+ Encodes an image file to a base64 string.
553
+
554
+ Args:
555
+ image_path (str): Path to the image file.
556
+
557
+ Returns:
558
+ str: Base64 encoded image string.
559
+ """
560
+ image = Image.open(image_path)
561
+ buffered = BytesIO()
562
+ image.save(buffered, format="JPEG")
563
+ return base64.b64encode(buffered.getvalue()).decode("utf-8")
564
+
565
+ def query_gpt4o_mini(self, query, image_path):
566
+ """
567
+ Queries the OpenAI GPT-4o-mini model with a query and images.
568
+
569
+ Args:
570
+ query (str): The user query.
571
+ image_path (list): List of image file paths.
572
+
573
+ Returns:
574
+ str: The AI response.
575
+ """
576
+ try:
577
+ base64_images = [self.encode_image_to_base64(pth) for pth in image_path]
578
+
579
+ response = openai_client.chat.completions.create(
580
+ model="gpt-4o-mini",
581
+ messages=[
582
+ {
583
+ "role": "user",
584
+ "content": [
585
+ {"type": "text", "text": PROMPT.format(query=query)}
586
+ ]
587
+ + [
588
+ {
589
+ "type": "image_url",
590
+ "image_url": {"url": f"data:image/jpeg;base64,{im}"},
591
+ }
592
+ for im in base64_images
593
+ ],
594
+ }
595
+ ],
596
+ max_tokens=500,
597
+ )
598
+ return response.choices[0].message.content
599
+ except Exception as err:
600
+ return f"Unable to generate the final output due to: {err}."
src/insurance_assistants/consts.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pyprojroot import find_root, has_file
2
+
3
+ PROJECT_ROOT_DIR = find_root(criterion=has_file("README.md"))
4
+
5
+ PROMPT = """
6
+ You are a smart assistant designed to answer questions about a PDF document.
7
+ You are given relevant information in the form of PDF pages. Use them to construct a short response to the question, and cite your sources (page numbers, etc).
8
+ If it is not possible to answer using the provided pages, do not attempt to provide an answer and simply say the answer is not present within the documents.
9
+ Give detailed and extensive answers, only containing info in the pages you are given.
10
+ You can answer using information contained in plots and figures if necessary.
11
+ Answer in the same language as the query.
12
+
13
+ Query: {query}
14
+ PDF pages:
15
+ """
16
+
17
+ PRIMARY_HEADING = """# An Agentic RAG for Health Insurance Documents
18
+ #### This agent answers health insurance related questions from pre-ingested set \
19
+ of health insurance documents, search wikipedia, search the web \
20
+ and execute basic python code. \
21
+
22
+ The pre-ingested health insurance documents can be viewed under `PDF Viewer` section.
23
+ """
24
+
25
+ PROMPT_PREFIX = """\n
26
+ Apart from all the above instructions that we have given to you, FOLLOW the Additional Instructions below:
27
+ ```
28
+ For any health insurance related queries, always use the `insurance_agent` first and return the results.
29
+ You are allowed to rephrase any query and detail it if required. When in doubt always ask the user a follow up question.
30
+ Don't assume anything.
31
+ ```
32
+
33
+ """
src/insurance_assistants/py.typed ADDED
File without changes
src/insurance_assistants/ui.py ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import mimetypes
3
+ import os
4
+ import re
5
+ import shutil
6
+
7
+ import gradio as gr
8
+ from dotenv import load_dotenv
9
+ from gradio_pdf import PDF
10
+ from smolagents.gradio_ui import _process_action_step, _process_final_answer_step
11
+ from smolagents.memory import ActionStep, FinalAnswerStep, MemoryStep, PlanningStep
12
+ from smolagents.models import ChatMessageStreamDelta
13
+
14
+ # from smolagents import CodeAgent, InferenceClientModel
15
+ from src.insurance_assistants.agents import manager_agent
16
+ from src.insurance_assistants.consts import PRIMARY_HEADING, PROJECT_ROOT_DIR
17
+
18
+ load_dotenv(override=True)
19
+
20
+ # Setup logger
21
+ logging.basicConfig(level=logging.INFO)
22
+ logger = logging.getLogger(__name__)
23
+
24
+
25
+ class UI:
26
+ """A one-line interface to launch your agent in Gradio"""
27
+
28
+ def __init__(self, file_upload_folder: str | None = None):
29
+ self.file_upload_folder = file_upload_folder
30
+ if self.file_upload_folder is not None:
31
+ if not os.path.exists(file_upload_folder):
32
+ os.mkdir(file_upload_folder)
33
+
34
+ def pull_messages_from_step(
35
+ self, step_log: MemoryStep, skip_model_outputs: bool = False
36
+ ):
37
+ """Extract ChatMessage objects from agent steps with proper nesting.
38
+
39
+ Args:
40
+ step_log: The step log to display as gr.ChatMessage objects.
41
+ skip_model_outputs: If True, skip the model outputs when creating the gr.ChatMessage objects:
42
+ This is used for instance when streaming model outputs have already been displayed.
43
+ """
44
+ if isinstance(step_log, ActionStep):
45
+ yield from _process_action_step(step_log, skip_model_outputs)
46
+ elif isinstance(step_log, PlanningStep):
47
+ pass
48
+ # yield from _process_planning_step(step_log, skip_model_outputs)
49
+ elif isinstance(step_log, FinalAnswerStep):
50
+ yield from _process_final_answer_step(step_log)
51
+ else:
52
+ raise ValueError(f"Unsupported step type: {type(step_log)}")
53
+
54
+ def stream_to_gradio(
55
+ self,
56
+ agent,
57
+ task: str,
58
+ task_images: list | None = None,
59
+ reset_agent_memory: bool = False,
60
+ additional_args: dict | None = None,
61
+ ):
62
+ """Runs an agent with the given task and streams the messages from the agent as gradio ChatMessages."""
63
+ intermediate_text = ""
64
+ for step_log in agent.run(
65
+ task,
66
+ images=task_images,
67
+ stream=True,
68
+ reset=reset_agent_memory,
69
+ additional_args=additional_args,
70
+ ):
71
+ # Track tokens if model provides them
72
+ if getattr(agent.model, "last_input_token_count", None) is not None:
73
+ if isinstance(step_log, (ActionStep, PlanningStep)):
74
+ step_log.input_token_count = agent.model.last_input_token_count
75
+ step_log.output_token_count = agent.model.last_output_token_count
76
+
77
+ if isinstance(step_log, MemoryStep):
78
+ intermediate_text = ""
79
+ for message in self.pull_messages_from_step(
80
+ step_log,
81
+ # If we're streaming model outputs, no need to display them twice
82
+ skip_model_outputs=getattr(agent, "stream_outputs", False),
83
+ ):
84
+ yield message
85
+ elif isinstance(step_log, ChatMessageStreamDelta):
86
+ intermediate_text += step_log.content or ""
87
+ yield intermediate_text
88
+
89
+ def interact_with_agent(self, prompt, messages, session_state):
90
+ # Get or create session-specific agent
91
+ if "agent" not in session_state:
92
+ # session_state["agent"] = CodeAgent(tools=[], model=InfenceClientModel())
93
+ session_state["agent"] = manager_agent
94
+ # Adding monitoring
95
+ try:
96
+ # log the existence of agent memory
97
+ has_memory = hasattr(session_state["agent"], "memory")
98
+ logger.info(f"Agent has memory: {has_memory}")
99
+ if has_memory:
100
+ logger.info(f"Memory type: {type(session_state['agent'].memory)}")
101
+
102
+ messages.append(gr.ChatMessage(role="user", content=prompt))
103
+ yield messages
104
+
105
+ for msg in self.stream_to_gradio(
106
+ agent=session_state["agent"],
107
+ task=prompt,
108
+ reset_agent_memory=False,
109
+ ):
110
+ messages.append(msg)
111
+ yield messages
112
+ yield messages
113
+ except Exception as e:
114
+ logger.info(f"Error in interaction: {str(e)}")
115
+ raise
116
+
117
+ def upload_file(
118
+ self,
119
+ file,
120
+ file_uploads_log,
121
+ allowed_file_types=[
122
+ "application/pdf",
123
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
124
+ "text/plain",
125
+ ],
126
+ ):
127
+ """
128
+ Handle file uploads, default allowed types are .pdf, .docx, and .txt
129
+ """
130
+ if file is None:
131
+ return gr.Textbox("No file uploaded", visible=True), file_uploads_log
132
+
133
+ try:
134
+ mime_type, _ = mimetypes.guess_type(file.name)
135
+ except Exception as e:
136
+ return gr.Textbox(f"Error: {e}", visible=True), file_uploads_log
137
+
138
+ if mime_type not in allowed_file_types:
139
+ return gr.Textbox("File type disallowed", visible=True), file_uploads_log
140
+
141
+ # Sanitize file name
142
+ original_name = os.path.basename(file.name)
143
+ sanitized_name = re.sub(
144
+ r"[^\w\-.]", "_", original_name
145
+ ) # Replace any non-alphanumeric, non-dash, or non-dot characters with underscores
146
+
147
+ type_to_ext = {}
148
+ for ext, t in mimetypes.types_map.items():
149
+ if t not in type_to_ext:
150
+ type_to_ext[t] = ext
151
+
152
+ # Ensure the extension correlates to the mime type
153
+ sanitized_name = sanitized_name.split(".")[:-1]
154
+ sanitized_name.append("" + type_to_ext[mime_type])
155
+ sanitized_name = "".join(sanitized_name)
156
+
157
+ # Save the uploaded file to the specified folder
158
+ file_path = os.path.join(
159
+ self.file_upload_folder, os.path.basename(sanitized_name)
160
+ )
161
+ shutil.copy(file.name, file_path)
162
+
163
+ return gr.Textbox(
164
+ f"File uploaded: {file_path}", visible=True
165
+ ), file_uploads_log + [file_path]
166
+
167
+ def log_user_message(self, text_input, file_uploads_log):
168
+ return (
169
+ text_input
170
+ + (
171
+ f"\nYou have been provided with these files, which might be helpful or not: {file_uploads_log}"
172
+ if len(file_uploads_log) > 0
173
+ else ""
174
+ ),
175
+ gr.Textbox(
176
+ value="",
177
+ interactive=False,
178
+ placeholder="Please wait while the agent answers your question",
179
+ ),
180
+ gr.Button(interactive=False),
181
+ )
182
+
183
+ def list_pdfs(self, dir=PROJECT_ROOT_DIR / "data/policy_wordings"):
184
+ file_names = [f.name for f in dir.iterdir()]
185
+ return file_names
186
+
187
+ def interrupt_agent(self, session_state):
188
+ if "agent" not in session_state:
189
+ session_state["agent"] = manager_agent
190
+ agent = session_state["agent"]
191
+ agent.interrupt()
192
+ return
193
+
194
+ def display_pdf(self, pdf_selector):
195
+ return PDF(
196
+ value=(f"{PROJECT_ROOT_DIR}/data/policy_wordings/{pdf_selector}"),
197
+ label="PDF Viewer",
198
+ show_label=True,
199
+ )
200
+
201
+ def launch(self, **kwargs):
202
+ with gr.Blocks(fill_height=True) as demo:
203
+ gr.Markdown(value=PRIMARY_HEADING)
204
+
205
+ @gr.render()
206
+ def layout(request: gr.Request):
207
+ # Render layout with sidebar
208
+ with gr.Blocks(
209
+ fill_height=True,
210
+ ):
211
+ file_uploads_log = gr.State([])
212
+ with gr.Sidebar():
213
+ gr.Markdown(
214
+ value="""#### <span style="color:red"> The `interrupt` button doesn't stop the process instantaneously.</span>
215
+ <span style="color:green">You can continue to use the application upon pressing the interrupt button.</span>
216
+ """
217
+ )
218
+ with gr.Group():
219
+ gr.Markdown(
220
+ value="**Your question, please...**", container=True
221
+ )
222
+ text_input = gr.Textbox(
223
+ lines=3,
224
+ label="Your question, please...",
225
+ container=False,
226
+ placeholder="Enter your prompt here and press Shift+Enter or press `Run`",
227
+ )
228
+ run_btn = gr.Button(value="Run", variant="primary")
229
+ agent_interrup_btn = gr.Button(
230
+ value="Interrupt", variant="stop"
231
+ )
232
+
233
+ # If an upload folder is provided, enable the upload feature
234
+ if self.file_upload_folder is not None:
235
+ upload_file = gr.File(label="Upload a file")
236
+ upload_status = gr.Textbox(
237
+ label="Upload Status",
238
+ interactive=False,
239
+ visible=False,
240
+ )
241
+ upload_file.change(
242
+ fn=self.upload_file,
243
+ inputs=[upload_file, file_uploads_log],
244
+ outputs=[upload_status, file_uploads_log],
245
+ )
246
+
247
+ gr.HTML("<br><br><h4><center>Powered by:</center></h4>")
248
+ with gr.Row():
249
+ gr.HTML("""<div style="display: flex; align-items: center; gap: 8px; font-family: system-ui, -apple-system, sans-serif;">
250
+ <img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png" style="width: 32px; height: 32px; object-fit: contain;" alt="logo">
251
+ <a target="_blank" href="https://github.com/huggingface/smolagents"><b>huggingface/smolagents</b></a>
252
+ </div>""")
253
+
254
+ # Add session state to store session-specific data
255
+ session_state = gr.State({})
256
+ # Initialize empty state for each session
257
+ stored_messages = gr.State([])
258
+ chatbot = gr.Chatbot(
259
+ label="Health Insurance Agent",
260
+ type="messages",
261
+ avatar_images=(
262
+ None,
263
+ "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/smolagents/mascot_smol.png",
264
+ ),
265
+ resizeable=False,
266
+ scale=1,
267
+ elem_id="Insurance-Agent",
268
+ )
269
+ with gr.Group():
270
+ gr.Markdown("### 📈 PDF Viewer")
271
+ pdf_choices = self.list_pdfs()
272
+ pdf_selector = gr.Dropdown(
273
+ choices=pdf_choices,
274
+ label="Select a PDF",
275
+ info="Choose one",
276
+ show_label=True,
277
+ interactive=True,
278
+ )
279
+ pdf_viewer = PDF(
280
+ label="PDF Viewer",
281
+ show_label=True,
282
+ )
283
+ pdf_selector.change(
284
+ fn=self.display_pdf, inputs=pdf_selector, outputs=pdf_viewer
285
+ )
286
+
287
+ text_input.submit(
288
+ fn=self.log_user_message,
289
+ inputs=[text_input, file_uploads_log],
290
+ outputs=[stored_messages, text_input, run_btn],
291
+ ).then(
292
+ fn=self.interact_with_agent,
293
+ # Include session_state in function calls
294
+ inputs=[stored_messages, chatbot, session_state],
295
+ outputs=[chatbot],
296
+ ).then(
297
+ fn=lambda: (
298
+ gr.Textbox(
299
+ interactive=True,
300
+ placeholder="Enter your prompt here or press `Run`",
301
+ ),
302
+ gr.Button(interactive=True),
303
+ ),
304
+ inputs=None,
305
+ outputs=[text_input, run_btn],
306
+ )
307
+ run_btn.click(
308
+ fn=self.log_user_message,
309
+ inputs=[text_input, file_uploads_log],
310
+ outputs=[stored_messages, text_input, run_btn],
311
+ ).then(
312
+ fn=self.interact_with_agent,
313
+ # Include session_state in function calls
314
+ inputs=[stored_messages, chatbot, session_state],
315
+ outputs=[chatbot],
316
+ ).then(
317
+ fn=lambda: (
318
+ gr.Textbox(
319
+ interactive=True,
320
+ placeholder="Enter your prompt here or press `Run`",
321
+ ),
322
+ gr.Button(interactive=True),
323
+ ),
324
+ inputs=None,
325
+ outputs=[text_input, run_btn],
326
+ )
327
+ agent_interrup_btn.click(
328
+ fn=self.interrupt_agent,
329
+ inputs=[session_state],
330
+ )
331
+
332
+ demo.launch(debug=True, **kwargs)
333
+
334
+
335
+ # if __name__=="__main__":
336
+ # UI().launch(share=True,
337
+ # allowed_paths=[(PROJECT_ROOT_DIR /"data/policy_wordings").as_posix()])
uv.lock ADDED
The diff for this file is too large to render. See raw diff