Spaces:
Sleeping
Sleeping
Project setup
Browse files- .env.example +14 -0
- .gitignore +207 -0
- LICENSE +21 -0
- app.py +408 -0
- data/processed/company_chunks.pkl +3 -0
- data/processed/vector_store/index.faiss +3 -0
- data/processed/vector_store/index.pkl +3 -0
- data/raw_company_info/FAQ.csv +263 -0
- data/raw_company_info/info.md +197 -0
- requirements.txt +12 -0
- src/__init__.py +0 -0
- src/agent.py +657 -0
- src/config.py +109 -0
- src/data_loaders.py +208 -0
- src/retriever.py +66 -0
- src/text_processor.py +25 -0
- src/tools.py +161 -0
- src/utils.py +173 -0
- src/vector_store.py +23 -0
.env.example
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenAI Configuration
|
| 2 |
+
OPENAI_API_KEY = ""
|
| 3 |
+
OPENAI_BASE_URL= "https://models.inference.ai.azure.com"
|
| 4 |
+
|
| 5 |
+
# HuggingFace Configuration
|
| 6 |
+
HUGGINGFACE_HUB_TOKEN = ""
|
| 7 |
+
|
| 8 |
+
# Tavily Search Configuration
|
| 9 |
+
TAVILY_API_KEY = ""
|
| 10 |
+
|
| 11 |
+
# Application Configuration
|
| 12 |
+
LOG_LEVEL=INFO
|
| 13 |
+
MAX_RETRIES=3
|
| 14 |
+
REQUEST_TIMEOUT=30
|
.gitignore
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[codz]
|
| 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 |
+
#poetry.toml
|
| 110 |
+
|
| 111 |
+
# pdm
|
| 112 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 113 |
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
| 114 |
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
| 115 |
+
#pdm.lock
|
| 116 |
+
#pdm.toml
|
| 117 |
+
.pdm-python
|
| 118 |
+
.pdm-build/
|
| 119 |
+
|
| 120 |
+
# pixi
|
| 121 |
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
| 122 |
+
#pixi.lock
|
| 123 |
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
| 124 |
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
| 125 |
+
.pixi
|
| 126 |
+
|
| 127 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 128 |
+
__pypackages__/
|
| 129 |
+
|
| 130 |
+
# Celery stuff
|
| 131 |
+
celerybeat-schedule
|
| 132 |
+
celerybeat.pid
|
| 133 |
+
|
| 134 |
+
# SageMath parsed files
|
| 135 |
+
*.sage.py
|
| 136 |
+
|
| 137 |
+
# Environments
|
| 138 |
+
.env
|
| 139 |
+
.envrc
|
| 140 |
+
.venv
|
| 141 |
+
env/
|
| 142 |
+
venv/
|
| 143 |
+
ENV/
|
| 144 |
+
env.bak/
|
| 145 |
+
venv.bak/
|
| 146 |
+
|
| 147 |
+
# Spyder project settings
|
| 148 |
+
.spyderproject
|
| 149 |
+
.spyproject
|
| 150 |
+
|
| 151 |
+
# Rope project settings
|
| 152 |
+
.ropeproject
|
| 153 |
+
|
| 154 |
+
# mkdocs documentation
|
| 155 |
+
/site
|
| 156 |
+
|
| 157 |
+
# mypy
|
| 158 |
+
.mypy_cache/
|
| 159 |
+
.dmypy.json
|
| 160 |
+
dmypy.json
|
| 161 |
+
|
| 162 |
+
# Pyre type checker
|
| 163 |
+
.pyre/
|
| 164 |
+
|
| 165 |
+
# pytype static type analyzer
|
| 166 |
+
.pytype/
|
| 167 |
+
|
| 168 |
+
# Cython debug symbols
|
| 169 |
+
cython_debug/
|
| 170 |
+
|
| 171 |
+
# PyCharm
|
| 172 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 173 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 174 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 175 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 176 |
+
#.idea/
|
| 177 |
+
|
| 178 |
+
# Abstra
|
| 179 |
+
# Abstra is an AI-powered process automation framework.
|
| 180 |
+
# Ignore directories containing user credentials, local state, and settings.
|
| 181 |
+
# Learn more at https://abstra.io/docs
|
| 182 |
+
.abstra/
|
| 183 |
+
|
| 184 |
+
# Visual Studio Code
|
| 185 |
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
| 186 |
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
| 187 |
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
| 188 |
+
# you could uncomment the following to ignore the entire vscode folder
|
| 189 |
+
# .vscode/
|
| 190 |
+
|
| 191 |
+
# Ruff stuff:
|
| 192 |
+
.ruff_cache/
|
| 193 |
+
|
| 194 |
+
# PyPI configuration file
|
| 195 |
+
.pypirc
|
| 196 |
+
|
| 197 |
+
# Cursor
|
| 198 |
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
| 199 |
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
| 200 |
+
# refer to https://docs.cursor.com/context/ignore-files
|
| 201 |
+
.cursorignore
|
| 202 |
+
.cursorindexingignore
|
| 203 |
+
|
| 204 |
+
# Marimo
|
| 205 |
+
marimo/_static/
|
| 206 |
+
marimo/_lsp/
|
| 207 |
+
__marimo__/
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Moaz Eldsouky
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
app.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import asyncio
|
| 3 |
+
import logging
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
import os
|
| 8 |
+
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
|
| 9 |
+
|
| 10 |
+
# Configure logging
|
| 11 |
+
logging.basicConfig(
|
| 12 |
+
level=logging.INFO,
|
| 13 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 14 |
+
)
|
| 15 |
+
logger = logging.getLogger(__name__)
|
| 16 |
+
|
| 17 |
+
try:
|
| 18 |
+
from src.agent import safe_run_agent_streaming, safe_run_agent, clear_memory
|
| 19 |
+
from src.data_loaders import process_uploaded_file
|
| 20 |
+
from src.utils import initialize_knowledge_base
|
| 21 |
+
except ImportError as e:
|
| 22 |
+
logger.error(f"Failed to import required modules: {e}")
|
| 23 |
+
raise
|
| 24 |
+
|
| 25 |
+
# Initialize knowledge base on startup
|
| 26 |
+
logger.info("Initializing knowledge base...")
|
| 27 |
+
try:
|
| 28 |
+
knowledge_base = initialize_knowledge_base()
|
| 29 |
+
if knowledge_base:
|
| 30 |
+
logger.info("Knowledge base initialized successfully")
|
| 31 |
+
else:
|
| 32 |
+
logger.warning("Knowledge base initialization failed - some features may be limited")
|
| 33 |
+
except Exception as e:
|
| 34 |
+
logger.error(f"Knowledge base initialization error: {e}")
|
| 35 |
+
knowledge_base = None
|
| 36 |
+
|
| 37 |
+
# Global variable to store processed document context
|
| 38 |
+
processed_docs = []
|
| 39 |
+
|
| 40 |
+
async def chat_function_streaming(message: str, history: list):
|
| 41 |
+
"""Process user message through the agent with streaming and better error handling"""
|
| 42 |
+
if not message or not message.strip():
|
| 43 |
+
history.append([message, "عذراً، لم أتلقَ أي سؤال. يرجى إدخال سؤالك أو طلبك."])
|
| 44 |
+
yield history, ""
|
| 45 |
+
return
|
| 46 |
+
|
| 47 |
+
# Add user message to history with empty response
|
| 48 |
+
history.append([message, ""])
|
| 49 |
+
|
| 50 |
+
try:
|
| 51 |
+
# Prepare message with document context if available
|
| 52 |
+
message_to_agent = message
|
| 53 |
+
if processed_docs:
|
| 54 |
+
str_processed_docs = "\n".join([
|
| 55 |
+
f"{doc.page_content}\n{doc.metadata}"
|
| 56 |
+
for doc in processed_docs
|
| 57 |
+
])
|
| 58 |
+
message_to_agent = f"{message}\n\nThis is Information you can use:\n\n{str_processed_docs}"
|
| 59 |
+
|
| 60 |
+
# Stream the response
|
| 61 |
+
accumulated_response = ""
|
| 62 |
+
async for chunk in safe_run_agent_streaming(message_to_agent):
|
| 63 |
+
accumulated_response += chunk
|
| 64 |
+
history[-1][1] = accumulated_response
|
| 65 |
+
yield history, ""
|
| 66 |
+
|
| 67 |
+
except Exception as e:
|
| 68 |
+
logger.error(f"Error in chat_function_streaming: {e}")
|
| 69 |
+
history[-1][1] = f"عذراً، حدث خطأ: {str(e)}"
|
| 70 |
+
yield history, ""
|
| 71 |
+
|
| 72 |
+
def upload_and_process_file(file) -> str:
|
| 73 |
+
"""Process uploaded file and add to knowledge base"""
|
| 74 |
+
global processed_docs
|
| 75 |
+
|
| 76 |
+
if file is None:
|
| 77 |
+
return "لم يتم رفع أي ملف"
|
| 78 |
+
|
| 79 |
+
try:
|
| 80 |
+
# Gradio's file object has a .name attribute which is the path to the temporary file
|
| 81 |
+
file_path = Path(file) # file is already a path string in newer versions
|
| 82 |
+
|
| 83 |
+
# Validate file type
|
| 84 |
+
allowed_extensions = {'.pdf', '.txt', '.docx', '.doc'}
|
| 85 |
+
if file_path.suffix.lower() not in allowed_extensions:
|
| 86 |
+
return f"نوع الملف غير مدعوم: {file_path.suffix}. الأنواع المدعومة: {', '.join(allowed_extensions)}"
|
| 87 |
+
|
| 88 |
+
# Check file size (limit to 10MB)
|
| 89 |
+
file_size = file_path.stat().st_size
|
| 90 |
+
if file_size > 10 * 1024 * 1024: # 10MB
|
| 91 |
+
return "الملف كبير جداً. الحد الأقصى 10 ميجابايت."
|
| 92 |
+
|
| 93 |
+
# Process the uploaded file
|
| 94 |
+
new_documents = process_uploaded_file(file_path)
|
| 95 |
+
|
| 96 |
+
if new_documents:
|
| 97 |
+
# Extend the global processed_docs list with the new documents
|
| 98 |
+
processed_docs.extend(new_documents)
|
| 99 |
+
return f"تم معالجة الملف '{file_path.name}' بنجاح. تمت إضافة {len(new_documents)} وثيقة."
|
| 100 |
+
else:
|
| 101 |
+
return f"لم يتم العثور على محتوى قابل للمعالجة في الملف '{file_path.name}'"
|
| 102 |
+
|
| 103 |
+
except Exception as e:
|
| 104 |
+
logger.error(f"Error processing file {file}: {e}")
|
| 105 |
+
return f"خطأ في معالجة الملف '{file}': {str(e)}"
|
| 106 |
+
|
| 107 |
+
def clear_chat_memory_and_history():
|
| 108 |
+
"""Clear the conversation memory, processed documents, chat history, and upload status"""
|
| 109 |
+
global processed_docs
|
| 110 |
+
|
| 111 |
+
try:
|
| 112 |
+
clear_memory()
|
| 113 |
+
processed_docs = []
|
| 114 |
+
logger.info("Successfully cleared chat memory and history")
|
| 115 |
+
# Return empty chat history, clear status, and empty upload status
|
| 116 |
+
return [], "تم مسح الذاكرة بنجاح. بدأت محادثة جديدة!", ""
|
| 117 |
+
except Exception as e:
|
| 118 |
+
logger.error(f"Error clearing memory: {e}")
|
| 119 |
+
return [], f"خطأ في مسح الذاكرة: {str(e)}", ""
|
| 120 |
+
|
| 121 |
+
def validate_startup():
|
| 122 |
+
"""Validate system before launching"""
|
| 123 |
+
required_env_vars = ["OPENAI_API_KEY"]
|
| 124 |
+
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
| 125 |
+
|
| 126 |
+
if missing_vars:
|
| 127 |
+
error_msg = f"Missing required environment variables: {', '.join(missing_vars)}"
|
| 128 |
+
logger.error(error_msg)
|
| 129 |
+
raise ValueError(error_msg)
|
| 130 |
+
|
| 131 |
+
logger.info("Startup validation passed")
|
| 132 |
+
|
| 133 |
+
# Wrapper function to handle async streaming for Gradio
|
| 134 |
+
def chat_function_wrapper(message, history):
|
| 135 |
+
"""Wrapper to run the async streaming function"""
|
| 136 |
+
try:
|
| 137 |
+
# Create new event loop for this thread
|
| 138 |
+
loop = asyncio.new_event_loop()
|
| 139 |
+
asyncio.set_event_loop(loop)
|
| 140 |
+
|
| 141 |
+
try:
|
| 142 |
+
# Run the async generator
|
| 143 |
+
async_gen = chat_function_streaming(message, history)
|
| 144 |
+
|
| 145 |
+
# Iterate through the async generator
|
| 146 |
+
while True:
|
| 147 |
+
try:
|
| 148 |
+
result = loop.run_until_complete(async_gen.__anext__())
|
| 149 |
+
yield result
|
| 150 |
+
except StopAsyncIteration:
|
| 151 |
+
break
|
| 152 |
+
|
| 153 |
+
finally:
|
| 154 |
+
loop.close()
|
| 155 |
+
|
| 156 |
+
except Exception as e:
|
| 157 |
+
# Fallback to non-streaming if streaming fails
|
| 158 |
+
try:
|
| 159 |
+
# Check if we have processed documents
|
| 160 |
+
if processed_docs:
|
| 161 |
+
# Create string representation of processed documents
|
| 162 |
+
str_processed_docs = "\n".join([
|
| 163 |
+
f"{doc.page_content}\n{doc.metadata}"
|
| 164 |
+
for doc in processed_docs
|
| 165 |
+
])
|
| 166 |
+
message_to_agent = f"{message}\n\nThis is Information you can use:\n\n{str_processed_docs}"
|
| 167 |
+
# Pass message with document context to the agent
|
| 168 |
+
response = asyncio.run(safe_run_agent(message_to_agent))
|
| 169 |
+
else:
|
| 170 |
+
# Process normally without document context
|
| 171 |
+
response = asyncio.run(safe_run_agent(message))
|
| 172 |
+
|
| 173 |
+
# Add the new conversation to history
|
| 174 |
+
history.append([message, response])
|
| 175 |
+
yield history, ""
|
| 176 |
+
except Exception as fallback_error:
|
| 177 |
+
history.append([message, f"Error: {str(fallback_error)}"])
|
| 178 |
+
yield history, ""
|
| 179 |
+
|
| 180 |
+
def create_interface():
|
| 181 |
+
"""Create and configure the Gradio interface"""
|
| 182 |
+
|
| 183 |
+
# Custom CSS for full-screen responsive layout
|
| 184 |
+
custom_css = """
|
| 185 |
+
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
|
| 186 |
+
|
| 187 |
+
/* Global font */
|
| 188 |
+
* {
|
| 189 |
+
font-family: 'Roboto', sans-serif;
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
/* Better RTL support for Arabic */
|
| 193 |
+
.rtl {
|
| 194 |
+
direction: rtl;
|
| 195 |
+
text-align: right;
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
/* Apply Roboto font only to English content explicitly if needed */
|
| 199 |
+
:lang(en) {
|
| 200 |
+
font-family: 'Roboto', sans-serif;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
/* Responsive design for small screens */
|
| 204 |
+
@media (max-width: 768px) {
|
| 205 |
+
.gradio-row {
|
| 206 |
+
flex-direction: column !important;
|
| 207 |
+
}
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
/* 👇 Control font size in the input Textbox */
|
| 211 |
+
textarea {
|
| 212 |
+
font-size: 18px !important;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
/* 👇 Control font size in the Chatbot messages */
|
| 216 |
+
.message, .message-user, .message-ai {
|
| 217 |
+
font-size: 18px !important;
|
| 218 |
+
line-height: 1.6;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
/* 👇 Optional: Adjust file upload input and other text areas */
|
| 222 |
+
input[type="file"], .gr-textbox, .gr-textbox textarea {
|
| 223 |
+
font-size: 16px !important;
|
| 224 |
+
}
|
| 225 |
+
"""
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
# Create the Gradio interface with full-screen layout
|
| 229 |
+
with gr.Blocks(
|
| 230 |
+
title="الشفاء الرقمية للرعاية الصحية - المساعد الطبي",
|
| 231 |
+
css=custom_css,
|
| 232 |
+
theme=gr.themes.Soft()
|
| 233 |
+
) as interface:
|
| 234 |
+
|
| 235 |
+
# Arabic Header with RTL support
|
| 236 |
+
gr.Markdown(
|
| 237 |
+
"""
|
| 238 |
+
<div style="text-align: center; direction: rtl;">
|
| 239 |
+
|
| 240 |
+
# 🏥 الشفاء الرقمية للرعاية الصحية - المساعد الطبي
|
| 241 |
+
|
| 242 |
+
### اطرح أسئلة طبية أو ارفع وثائق للحصول على مساعدة
|
| 243 |
+
|
| 244 |
+
</div>
|
| 245 |
+
""",
|
| 246 |
+
elem_classes="rtl"
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
+
# Full-width responsive layout
|
| 250 |
+
with gr.Row():
|
| 251 |
+
with gr.Column(scale=3, min_width=400): # Increased scale for chat area
|
| 252 |
+
chatbot = gr.Chatbot(
|
| 253 |
+
label="💬 محادثة المساعد الطبي",
|
| 254 |
+
height=600, # Increased height
|
| 255 |
+
show_label=True,
|
| 256 |
+
container=True,
|
| 257 |
+
bubble_full_width=False,
|
| 258 |
+
rtl=True # Enable RTL for Arabic support
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
with gr.Row():
|
| 262 |
+
msg = gr.Textbox(
|
| 263 |
+
label="رسالتك",
|
| 264 |
+
placeholder="اطرح سؤالاً طبياً باللغة العربية أو الإنجليزية...",
|
| 265 |
+
scale=4,
|
| 266 |
+
container=False,
|
| 267 |
+
rtl=True
|
| 268 |
+
)
|
| 269 |
+
submit_btn = gr.Button("إرسال", variant="primary", scale=1)
|
| 270 |
+
|
| 271 |
+
gr.Markdown(
|
| 272 |
+
"""
|
| 273 |
+
<div style="text-align: center; direction: rtl; color: #666; margin-top: 10px;">
|
| 274 |
+
<em>
|
| 275 |
+
ملاحظة: هذا مشروع تجريبي شخصي لاغراض تعليمية فقط.
|
| 276 |
+
<br>
|
| 277 |
+
لإنشاء مشروع مماثل، يمكنكم التواصل مع المطور:
|
| 278 |
+
<br>
|
| 279 |
+
<a href="mailto:moazeldsoky8@gmail.com">Email</a> | <a href="https://github.com/MoazEldsouky">GitHub</a> | <a href="https://www.linkedin.com/in/moaz-eldesouky-762288251/">LinkedIn</a>
|
| 280 |
+
<br>
|
| 281 |
+
WhatsApp: +201096448317
|
| 282 |
+
<br>
|
| 283 |
+
في حالات الطوارئ، اتصل بـ 997 فوراً.
|
| 284 |
+
</em>
|
| 285 |
+
</div>
|
| 286 |
+
""",
|
| 287 |
+
elem_classes="rtl"
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
with gr.Column(scale=2, min_width=300): # Side panel
|
| 291 |
+
# Document Upload Section
|
| 292 |
+
gr.Markdown("### 📁 رفع الوثائق", elem_classes="rtl")
|
| 293 |
+
file_upload = gr.File(
|
| 294 |
+
label="ارفع وثيقة طبية",
|
| 295 |
+
file_types=[".pdf", ".txt", ".docx", ".doc"],
|
| 296 |
+
type="filepath"
|
| 297 |
+
)
|
| 298 |
+
upload_status = gr.Textbox(
|
| 299 |
+
label="حالة الرفع",
|
| 300 |
+
interactive=False,
|
| 301 |
+
max_lines=3,
|
| 302 |
+
rtl=True
|
| 303 |
+
)
|
| 304 |
+
|
| 305 |
+
# Memory Management Section
|
| 306 |
+
gr.Markdown("### 🧠 إدارة الذاكرة", elem_classes="rtl")
|
| 307 |
+
clear_btn = gr.Button(
|
| 308 |
+
"🗑️ مسح الذاكرة وبدء محادثة جديدة",
|
| 309 |
+
variant="secondary",
|
| 310 |
+
size="lg"
|
| 311 |
+
)
|
| 312 |
+
clear_status = gr.Textbox(
|
| 313 |
+
label="حالة المسح",
|
| 314 |
+
interactive=False,
|
| 315 |
+
rtl=True
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
# About section in Arabic
|
| 319 |
+
with gr.Accordion("ℹ️ حول مساعد الشفاء الرقمي", open=False):
|
| 320 |
+
gr.Markdown(
|
| 321 |
+
"""
|
| 322 |
+
<div style="direction: rtl; text-align: right;">
|
| 323 |
+
|
| 324 |
+
**الميزات:**
|
| 325 |
+
- معلومات وإرشادات طبية
|
| 326 |
+
- مساعدة في حجز المواعيد
|
| 327 |
+
- دعم تحليل الوثائق
|
| 328 |
+
- دعم ثنائي اللغة (العربية/الإنجليزية)
|
| 329 |
+
- متاح 24/7 لخدمتكم
|
| 330 |
+
|
| 331 |
+
**مهم:**
|
| 332 |
+
- هذا ليس بديلاً عن المشورة الطبية المهنية
|
| 333 |
+
- في حالات الطوارئ، اتصل دائماً بـ 997
|
| 334 |
+
- الاستجابات مولدة بالذكاء الاصطناعي ويجب التحقق منها مع المختصين الطبيين
|
| 335 |
+
|
| 336 |
+
**معلومات الاتصال:**
|
| 337 |
+
- الطوارئ: 997
|
| 338 |
+
- خدمة العملاء: متوفرة على مدار الساعة
|
| 339 |
+
- الموقع الإلكتروني: www.alshifadigital.com
|
| 340 |
+
- الهاتف: 9200-000-000 (متوفر خلال ساعات العمل الرسمية)
|
| 341 |
+
|
| 342 |
+
</div>
|
| 343 |
+
""",
|
| 344 |
+
elem_classes="rtl"
|
| 345 |
+
)
|
| 346 |
+
|
| 347 |
+
# Event handlers with streaming support
|
| 348 |
+
def submit_message(message, history):
|
| 349 |
+
"""Handle message submission"""
|
| 350 |
+
if message.strip():
|
| 351 |
+
yield from chat_function_wrapper(message, history)
|
| 352 |
+
|
| 353 |
+
# Connect the submit events
|
| 354 |
+
msg.submit(
|
| 355 |
+
submit_message,
|
| 356 |
+
inputs=[msg, chatbot],
|
| 357 |
+
outputs=[chatbot, msg]
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
submit_btn.click(
|
| 361 |
+
submit_message,
|
| 362 |
+
inputs=[msg, chatbot],
|
| 363 |
+
outputs=[chatbot, msg]
|
| 364 |
+
)
|
| 365 |
+
|
| 366 |
+
# File upload handler
|
| 367 |
+
file_upload.upload(
|
| 368 |
+
upload_and_process_file,
|
| 369 |
+
inputs=file_upload,
|
| 370 |
+
outputs=upload_status
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
# Clear memory handler - now clears memory, chat history, and upload status
|
| 374 |
+
clear_btn.click(
|
| 375 |
+
clear_chat_memory_and_history,
|
| 376 |
+
inputs=[],
|
| 377 |
+
outputs=[chatbot, clear_status, upload_status]
|
| 378 |
+
)
|
| 379 |
+
|
| 380 |
+
return interface
|
| 381 |
+
|
| 382 |
+
def launch_gradio():
|
| 383 |
+
"""Launch Gradio with startup validation"""
|
| 384 |
+
try:
|
| 385 |
+
validate_startup()
|
| 386 |
+
logger.info("Starting Gradio interface...")
|
| 387 |
+
|
| 388 |
+
interface = create_interface()
|
| 389 |
+
|
| 390 |
+
interface.launch(
|
| 391 |
+
server_name="0.0.0.0",
|
| 392 |
+
server_port=int(os.getenv("PORT", 7860)),
|
| 393 |
+
share=bool(os.getenv("GRADIO_SHARE", False)),
|
| 394 |
+
debug=bool(os.getenv("DEBUG", False)),
|
| 395 |
+
show_error=True,
|
| 396 |
+
quiet=False,
|
| 397 |
+
inbrowser=True,
|
| 398 |
+
favicon_path=None,
|
| 399 |
+
ssl_verify=False,
|
| 400 |
+
app_kwargs={}
|
| 401 |
+
)
|
| 402 |
+
|
| 403 |
+
except Exception as e:
|
| 404 |
+
logger.error(f"Failed to launch Gradio: {e}")
|
| 405 |
+
raise
|
| 406 |
+
|
| 407 |
+
if __name__ == "__main__":
|
| 408 |
+
launch_gradio()
|
data/processed/company_chunks.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f20d3f4e3b7988cf855309ee7689acdee4b5bfa1ed98f07162d660be39d8df3e
|
| 3 |
+
size 112648
|
data/processed/vector_store/index.faiss
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b031bcd9822372b42f9693b991ff081f3dbdadc762abb5acae174b38c7357191
|
| 3 |
+
size 420909
|
data/processed/vector_store/index.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2ca52b169948f44e3546b84a7c8f008b731f501547df6604a6d5017dd0f47af6
|
| 3 |
+
size 125794
|
data/raw_company_info/FAQ.csv
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Question,Answer
|
| 2 |
+
ما هي شركة الشفاء الرقمية؟,هي شركة رائدة ومتطورة في مجال الرعاية الصحية عن بُعد (Telehealth)، تأسست لتوفير رعاية طبية شاملة وعالية الجودة عبر الإنترنت.
|
| 3 |
+
ما هي رؤية الشركة؟,أن تكون الخيار الأول والموثوق به عالميًا في مجال الرعاية الصحية الرقمية، وأن تُحدِث فرقًا حقيقيًا وملموسًا في جودة حياة الأفراد والمجتمعات.
|
| 4 |
+
ما هي رسالة الشركة؟,تقديم خدمات طبية موثوقة، آمنة، وسهلة الوصول لجميع أفراد المجتمع، مع التركيز على الكفاءة المهنية والشفافية التامة.
|
| 5 |
+
ما هي القيم الأساسية التي تلتزم بها الشركة؟,السرية والخصوصية، الجودة والاحترافية، التيسير والسرعة، الموثوقية والشفافية، التعاطف والرعاية، والابتكار والتطوير المستمر.
|
| 6 |
+
هل بياناتي محفوظة وآمنة؟,نعم، تلتزم الشركة بأعلى معايير الأمان والخصوصية العالمية لحماية البيانات الشخصية والطبية، وتستخدم تقنيات التشفير المتقدمة (SSL/TLS).
|
| 7 |
+
ما هي المؤهلات التي يتمتع بها الأطباء في الشركة؟,جميع أطبائنا حاصلون على شهادات معتمدة ولديهم سنوات من الخبرة في تخصصاتهم، ويخضعون لعملية تدقيق صارمة لضمان أعلى مستويات الكفاءة والاحترافية.
|
| 8 |
+
ما هي الخدمات التي تقدمها الشركة؟,تشمل الخدمات الاستشارات الطبية عبر الفيديو، الكشف الجسدي في العيادة، مراجعة التقارير الطبية والتحاليل، خدمات الصيدلة والتوصيل المنزلي، حجز مواعيد مع أطباء متخصصين، وخدمات الرعاية النفسية والدعم الصحي. كما تقدم الشركة تحليل نتائج المختبر، نظام السجلات الطبية الإلكترونية الموحدة (EHR)، برامج الصحة الوقائية، وخدمات الطب عن بُعد للمؤسسات والشركات.
|
| 9 |
+
هل يمكن الحصول على استشارة طبية في حالات الطوارئ؟,لا، خدمات شركة الشفاء الرقمية مخصصة للحالات غير الطارئة والاستشارات الروتينية والمتابعة فقط. يجب التوجه فوراً إلى أقرب مستشفى أو الاتصال برقم الطوارئ في حالة وجود أي حالة طارئة.
|
| 10 |
+
ما هي اللغات التي يقدم بها الأطباء الاستشارات؟,يقدم أطباؤنا الاستشارات باللغة العربية بشكل أساسي، بالإضافة إلى إمكانية توفير استشارات بلغات أخرى مثل الإنجليزية حسب توفر الأطباء المتحدثين بهذه اللغات.
|
| 11 |
+
هل يمكنني اختيار الطبيب الذي أرغب في الاستشارة معه؟,نعم، يمكنك تصفح ملفات الأطباء المتوفرين، والاطلاع على تخصصاتهم، خبراتهم، وتقييمات المرضى الآخرين لاختيار الأنسب.
|
| 12 |
+
هل يمكنني حجز استشارة لأحد أفراد عائلتي؟,نعم، يمكنك حجز استشارة نيابة عن أحد أفراد عائلتك (مثل الأطفال أو كبار السن) مع مراعاة الحصول على موافقتهم الصريحة وتقديم معلوماتهم الطبية بدقة.
|
| 13 |
+
هل يمكنني استرداد المبلغ إذا لم أكن راضيًا عن الخدمة؟,نعم، يمكن طلب استرداد المبلغ خلال 24 ساعة من تقديم الخدمة أو عدم إتمامها، وذلك وفقًا لسياسة الاسترداد الخاصة بالشركة.
|
| 14 |
+
هل تشمل الاستشارات وصف الأدوية؟,نعم، إذا رأى الطبيب ضرورة طبية لذلك، يمكنه وصف الأدوية اللازمة خلال الاستشارة. يتم إرسال الوصفة إلكترونيًا ويمكن توصيلها إليك مباشرة عبر خدمة الصيدلية الإلكترونية.
|
| 15 |
+
هل توفرون فحصاً طبياً شاملاً عن بعد؟,لا يمكن إجراء فحص جسدي شامل عن بعد. ولكن، توفر الشركة خدمة الكشف الجسدي في عيادتها الخاصة في جميع التخصصات.
|
| 16 |
+
ما هو عنوان العيادة الخاصة بكم؟,عنوان العيادة هو: الرياض، حي الربيع، طريق الثمامة، المملكة العربية السعودية.
|
| 17 |
+
ما هي طرق الدفع المتاحة؟,نقبل الدفع عبر البطاقات البنكية (فيزا / ماستركارد / مدى) والمحافظ الرقمية (Apple Pay و STC Pay). كما يمكن الدفع عن طريق التحويل البنكي لبعض الخدمات أو الباقات الكبيرة.
|
| 18 |
+
هل تقدمون باقات اشتراك؟,نعم، نوفر خيارات اشتراك شهرية أو سنوية توفر خصومات إضافية ووصولاً غير محدود لبعض الخدمات.
|
| 19 |
+
ما هي خدمات الدعم الفني المتاحة؟,يتوفر الدعم الفني عبر الدردشة المباشرة (24/7) على الموقع الإلكتروني، والهاتف خلال ساعات العمل، والبريد الإلكتروني. كما يوجد قسم للمساعدة والأسئلة الشائعة.
|
| 20 |
+
هل يمكنكم توصيل الأدوية للمنزل؟,نعم، نوفر خدمة توصيل الأدوية والمستلزمات الطبية إلى باب المنزل بسرعة وأمان.
|
| 21 |
+
هل يمكنني مراجعة تقاريري الطبية ونتائج تحاليلي مع طبيب؟,نعم، تقدم الشركة خدمة مراجعة وتقييم للتقارير الطبية السابقة ونتائج التحاليل المخبرية من قبل أطباء متخصصين.
|
| 22 |
+
ما هي برامج الصحة الوقائية التي تقدمونها؟,تقدم الشركة استشارات حول الوقاية من الأمراض وتغيير نمط الحياة، بالإضافة إلى حملات توعية صحية دورية وبرامج مخصصة لمتابعة المؤشرات الحيوية.
|
| 23 |
+
هل تقدمون خدمات للشركات؟,نعم، نوفر حزم رعاية صحية رقمية للشركات والمؤسسات لرعاية موظفيهم، وبرامج صحية مخصصة لتعزيز رفاهية الموظفين.
|
| 24 |
+
كيف يمكنني الاشتراك في خدماتكم؟,يمكنك الاشتراك بزيارة الموقع الإلكتروني وإنشاء حساب، ثم اختيار الباقة المناسبة، وإدخال معلومات الدفع.
|
| 25 |
+
كيف يمكنني إلغاء الاشتراك؟,يمكن إلغاء الاشتراك عبر تسجيل الدخول إلى حسابك على الموقع ثم التوجه إلى "إدارة الاشتراكات". أو يمكنك التواصل مع فريق الدعم الفني للمساعدة في إتمام عملية الإلغاء.
|
| 26 |
+
هل توفرون خدمة السجلات الطبية الإلكترونية؟,نعم، لدينا نظام آمن لتخزين السجل الطبي الموحد للمريض، بما في ذلك التشخيصات، الأدوية الموصوفة، ونتائج الفحوصات.
|
| 27 |
+
ما هي أوقات عمل الشركة؟,أيام العمل من الأحد إلى الخميس، من 9 صباحًا حتى 9 مساءً بتوقيت المملكة العربية السعودية (EEST).
|
| 28 |
+
هل الشركة تعمل في العطلات الأسبوعية؟,لا، العطلات الأسبوعية هي الجمعة والسبت.
|
| 29 |
+
ما هي سياسة الخصوصية المتعلقة بجمع البيانات؟,يتم جمع البيانات الشخصية والمعلومات الصحية فقط لغرض تقديم الخدمات الطبية وتحسينها، وبموافقة صريحة من المستخدم.
|
| 30 |
+
هل تتم مشاركة بياناتي مع أطراف ثالثة؟,لا يتم مشاركة البيانات الشخصية أو الصحية مع أي طرف ثالث دون إذن مسبق وصريح من المستخدم، إلا في الحالات التي يتطلبها القانون أو بأمر قضائي.
|
| 31 |
+
هل يمكنني تعديل أو حذف بياناتي؟,نعم، يحق للمستخدم الوصول إلى بياناته الشخصية وتعديلها أو طلب حذفها في أي وقت.
|
| 32 |
+
ما هي الشروط التي تحكم استخدام خدمات الشركة؟,من الشروط الأساسية عدم استخدام الخدمة في حالات الطوارئ، وتقديم معلومات صحيحة وكاملة ودقيقة، وأن الشركة غير مسؤولة عن أي سوء استخدام للخدمة.
|
| 33 |
+
هل يمكن الحصول على استشارة بخصوص مشاكل الجهاز الهضمي؟,نعم، الشركة تقدم استشارات مع أطباء عامين ومتخصصين في مختلف التخصصات مثل الباطنية، والتي تشمل مشاكل الجهاز الهضمي.
|
| 34 |
+
هل يمكنني الحصول على استشارة بخصوص مشاكل العيون؟,نعم، من بين التخصصات التي يقدمها أطباؤنا هو تخصص العيون.
|
| 35 |
+
هل يمكنني الحصول على استشارة بخصوص مشاكل الأنف والأذن والحنجرة؟,نعم، توفر الشركة استشارات مع أطباء متخصصين في الأنف والأذن والحنجرة.
|
| 36 |
+
هل يمكنني الحصول على استشارة بخصوص الأمراض الجلدية؟,نعم�� لدينا أطباء متخصصون في الأمراض الجلدية.
|
| 37 |
+
هل يمكنني الحصول على استشارة بخصوص صحة المرأة والولادة؟,نعم، نوفر استشارات مع أطباء نساء وولادة.
|
| 38 |
+
هل يمكنني طلب الأدوية التي لا تتطلب وصفة طبية؟,نعم، يمكنك طلب الأدوية التي لا تتطلب وصفة طبية مباشرة من الصيدلية الرقمية.
|
| 39 |
+
هل يوجد دعم للصحة النفسية؟,نعم، نوفر جلسات استشارية مع أخصائيين نفسيين ومعالجين سلوكيين لدعم الصحة النفسية.
|
| 40 |
+
هل يمكنني الحصول على دعم بخصوص التغذية واللياقة البدنية؟,نعم، تقدم الشركة استشارات في مجالات الصحة العامة، التغذية، واللياقة البدنية.
|
| 41 |
+
ما هو مبدأ تأسيس شركة الشفاء الرقمية؟,تأسست الشركة على مبدأ توفير رعاية طبية شاملة وعالية الجودة عبر الإنترنت.
|
| 42 |
+
ما هو هدف الشركة الأساسي؟,تهدف إلى سد الفجوة بين المرضى والأطباء المتخصصين، وتمكين الأفراد من الحصول على استشارات طبية موثوقة ومتابعة مستمرة.
|
| 43 |
+
كيف تضمنون جودة الرعاية الصحية؟,نضمن أن جميع خدماتنا الطبية تُقدم من قبل أطباء ومتخصصين مؤهلين تأهيلاً عاليًا ويتمتعون بخبرة واسعة في مجالاتهم.
|
| 44 |
+
ما هو الالتزام بالسرية والخصوصية؟,نلتزم بأقصى درجات السرية في التعامل مع بيانات المرضى ومعلوماتهم الصحية، ونطبق بروتوكولات أمان صارمة لضمان حمايتها.
|
| 45 |
+
ماذا يعني "التيسير والسرعة" كقيمة أساسية؟,يعني أن الشركة تهدف إلى جعل الرعاية الصحية سهلة ومتاحة للجميع، بغض النظر عن موقعهم الجغرافي، وتوفر حلولاً سريعة وفعالة للحصول على الاستشارات والمواعيد.
|
| 46 |
+
كيف تبني الشركة الثقة مع المرضى؟,تبني الشركة الثقة من خلال تقديم معلومات واضحة وصادقة حول خدماتها وتكاليفها، وتحرص على بناء علاقات طويلة الأمد قائمة على المصداقية والنزاهة.
|
| 47 |
+
ما هو دور الابتكار في الشركة؟,نتبنى أحدث التقنيات ونبحث دائمًا عن طرق جديدة ومبتكرة لتحسين خدماتنا وتوسيع نطاقها لمواكبة التطورات في مجال الرعاية الصحية الرقمية.
|
| 48 |
+
ماذا تشمل خدمة الاستشارات الطبية عبر الفيديو؟,تشمل استشارات فورية ومجدولة مع أطباء عامين ومتخصصين، وإمكانية التواصل الصوتي والمرئي الآمن والمشفر، وخاصية تبادل الملفات والصور.
|
| 49 |
+
هل يمكنني إجراء فحص جسدي في عيادتكم لجميع التخصصات؟,نعم، تقدم الشركة خدمات الكشف الجسدي في عيادتها الخاصة في جميع التخصصات.
|
| 50 |
+
هل هناك خدمة لمراجعة نتائج التحاليل المخبرية؟,نعم، نقدم خدمة مراجعة وتقييم لنتائج التحاليل المخبرية من قبل أطباء متخصصين لتقديم تفسير واضح وشامل.
|
| 51 |
+
ما هي وظيفة خدمة الصيدلة والتوصيل المنزلي؟,تشمل صرف الوصفات الطبية الإلكترونية وتوصيل الأدوية والمستلزمات الطبية إلى باب المنزل بسرعة وأمان.
|
| 52 |
+
كيف يمكنني حجز موعد مع طبيب متخصص؟,يمكنك استخدام منصة سهلة الاستخدام لحجز مواعيد مع مجموعة واسعة من الأطباء الاستشاريين.
|
| 53 |
+
ماذا تتضمن خدمة الرعاية النفسية؟,تتضمن جلسات استشارية مع أخصائيين نفسيين ومعالجين سلوكيين، وتقديم استشارات في مجالات الصحة العامة وإدارة الإجهاد.
|
| 54 |
+
ما هي فوائد نظام السجلات الطبية الإلكترونية الموحدة (EHR)؟,يخزن السجل الطبي للمريض بشكل آمن، ويمكّن الأطباء من الوصول إليه لتوفير رعاية أكثر دقة، ويقلل من الحاجة لتكرار الفحوصات.
|
| 55 |
+
ما هي برامج الطب عن بُعد التي تقدمونها للمؤسسات؟,نقدم حزم رعاية صحية رقمية للشركات لرعاية موظفيهم، وبرامج صحية مخصصة لتعزيز رفاهيتهم.
|
| 56 |
+
ما هو رقم هاتف الشركة؟,رقم هاتف الشركة هو 9200-000-000.
|
| 57 |
+
ما هو البريد الإلكتروني للدعم؟,البريد الإلكتروني للدعم هو support@alshifa-care.com.
|
| 58 |
+
هل يمكنني استخدام الدردشة المباشرة للدعم الفني؟,نعم، الدردشة المباشرة متاحة على مدار الساعة (24/7) على الموقع الإلكتروني لتقديم المساعدة الفورية.
|
| 59 |
+
ما هي المحافظ الرقمية التي تقبلونها للدفع؟,نقبل الدفع السريع والآمن عبر Apple Pay و STC Pay.
|
| 60 |
+
ماذا يحدث إذا قدمت معلومات طبية غير دقيقة؟,أي معلومات غير دقيقة قد تؤثر سلبًا على جودة الرعاية المقدمة، والشركة غير مسؤولة عن أي نتائج سلبية تنجم عن ذلك.
|
| 61 |
+
هل يمكنني استخدام محتوى الموقع لأغراض شخصية؟,جميع المحتويات والمواد على المنصة هي ملك للشركة، ويُمنع نسخها أو تعديلها أو إعادة نشرها دون إذن كتابي مسبق.
|
| 62 |
+
هل تحتفظ الشركة بالحق في تعديل شروط الاستخدام؟,نعم، تحتفظ الشركة بالحق في تعديل شروط الاستخدام في أي وقت دون إشعار مسبق.
|
| 63 |
+
بموجب أي قانون يتم تفسير شروط الاستخدام؟,تخضع شروط الاستخدام وتُفسر وفقًا لقوانين المملكة العربية السعودية.
|
| 64 |
+
كيف يتم إخطار المستخدمين بالتحديثات على سياسة الخصوصية؟,سيتم إخطار المستخدمين بأي تغييرات جوهرية في سياسة الخصوصية من خلال الموقع الإلكتروني أو البريد الإلكتروني.
|
| 65 |
+
هل يمكنني التحكم في إعدادات ملفات تعريف الارتباط؟,نعم، يمكن للمستخدمين التحكم في إعدادات ملفات تعريف الارتباط (Cookies) من خلال متصفحاتهم.
|
| 66 |
+
هل يمكنني حجز موعد مع أطباء في تخصصات نادرة؟,نعم، المنصة تمكّنك من حجز مواعيد مع مجموعة واسعة من الأطباء الاستشاريين في تخصصات نادرة أو دقيقة.
|
| 67 |
+
ما هو الغرض من استخدام البيانات المجمعة (التي لا تحدد هوية فردية)؟,قد تُستخدم البيانات المجمعة لأغراض البحث والإحصاءات لتحسين الخدمات الصحية بشكل عام.
|
| 68 |
+
هل يمكنني حجز موعد للحصول على استشارة مع طبيب عام؟,نعم، يمكنك حجز استشارات فورية ومجدولة مع أطباء عامين.
|
| 69 |
+
كيف يمكنني العثور على الأطباء المناسبين لي؟,يمكنك الاطلاع على الملفات الشخصية للأطباء، خبراتهم، وتقييمات المرضى الآخرين لاختيار الأنسب.
|
| 70 |
+
ما هي أنواع الخدمات التي يمكن أن أحصل عليها عبر الفيديو؟,يمكنك الحصول على استشارات فورية أو مجدولة مع أطباء عامين ومتخصصين، بالإضافة إلى جلسات متابعة مستمرة للحالات المزمنة.
|
| 71 |
+
هل يتم تشفير الاتصالات عبر الفيديو؟,نعم، يتم تشفير التواصل الصوتي والمرئي بشكل آمن ومشفر.
|
| 72 |
+
هل يمكن تبادل الملفات والصور خلال الاستشارة؟,نعم، تتوفر خاصية تبادل الملفات والصور لتحسين جودة التشخيص.
|
| 73 |
+
هل يمكنني حجز مواعيد للكشف الجسدي في العيادة؟,نعم، يمكنك حجز المواعيد المسبقة لضمان حصولك على الرعاية اللازمة في الوقت المناسب.
|
| 74 |
+
هل يمكنني الحصول على شرح مفصل لنتائج تحاليلي المخبرية؟,نعم، تقدم الشركة شرحًا مفصلاً لنتائج التحاليل المخبرية وربطها بالحالة الصحية العامة للمريض.
|
| 75 |
+
هل يمكنني الحصول على توصيات بناءً على نتائج تحاليلي؟,نعم، يتم تقديم توصيات بناءً على النتائج لمساعدة المرضى على فهم حالتهم الصحية بشكل أفضل.
|
| 76 |
+
ما هو هدف الشركة من برامج الصحة الوقائية؟,الهدف هو توعية صحية دورية حول الأمراض الشائعة وطرق الوقاية منها.
|
| 77 |
+
ماذا يحدث في حالة عدم اتباع إرشادات الطبيب؟,الشركة غير مسؤولة عن أي نتائج سلبية تنجم عن عدم اتباع إرشادات الطبيب.
|
| 78 |
+
هل يمكنني استخدام المنصة باللغة الإنجليزية؟,نعم، يمكن توفير استشارات باللغة الإنجليزية حسب توفر الأطباء المتحدثين بهذه اللغات.
|
| 79 |
+
ما هو هدف الشركة من بناء مجتمع صحي واعي؟,تطمح الشركة إلى بناء مجتمع صحي واعي، حيث تكون الوقاية والعلاج السريع جزءًا لا يتجزأ من الثقافة العامة.
|
| 80 |
+
ما هو الغرض من السجلات الطبية الإلكترونية الموحدة؟,يقلل النظام من الحاجة لتكرار الفحوصات ويسهل عملية المتابعة.
|
| 81 |
+
ما هي الالتزامات القانونية التي تتبعها الشركة في حماية البيانات؟,تلتزم الشركة بجميع اللوائح والقوانين المتعلقة بحماية البيانات الصحية، مثل قانون حماية البيانات العامة (GDPR) ومعايير HIPAA حيثما ينطبق ذلك.
|
| 82 |
+
ما هو التشفير المستخدم لحماية البيانات؟,يتم تشفير جميع الاتصالات والبيانات باستخدام أحدث بروتوكولات الأمان مثل AES-256 و TLS 1.2.
|
| 83 |
+
ما هي حقوق المستخدم بخصوص بياناته؟,يحق للمستخدم الوصول إلى بياناته الشخصية، وتعديلها، أو طلب حذفها في أي وقت.
|
| 84 |
+
هل يمكن للمستخدم سحب موافقته على معالجة البيانات؟,نعم، يحق للمستخدم سحب موافقته على معالجة البيانات، شريطة ألا يتعارض ذلك مع الالتزامات القانونية.
|
| 85 |
+
هل يمكنني استخدام خدمات الشركة في عطلة رسمية؟,قد تكون الشركة مغلقة خلال الأعياد الرسمية في المملكة العربية السعودية، ولكن قد تتوفر بعض خدمات الدعم الفني أو الدردشة المباشرة.
|
| 86 |
+
كيف يمكنني معرفة التغييرات في أوقات العمل خلال العطلات؟,سيتم الإعلان عن أي تغييرات في أوقات العمل خلال العطلات مسبقًا عبر الموقع ووسائل التواصل الاجتماعي.
|
| 87 |
+
ماذا يحدث بعد إتمام عملية الاشتراك؟,ستتلقى تأكيدًا عبر البريد الإلكتروني، ويمكنك البدء في حجز المواعيد والاستفادة من الخدمات.
|
| 88 |
+
هل هناك سياسات مختلفة للإلغاء حسب نوع الاشتراك؟,نعم، يرجى مراجعة شروط الاشتراك في كل باقة، حيث قد تختلف سياسات الإلغاء والاسترداد بناءً على نوع الخدمة أو مدة الاشتراك.
|
| 89 |
+
ما هو الغرض من استخدام ملفات تعريف الارتباط (Cookies)؟,نستخدم ملفات تعريف الارتباط لتحسين تجربة التصفح، تحليل أداء الموقع، وتخصيص المحتوى.
|
| 90 |
+
هل يمكنني الحصول على توصيات بخصوص نمط الحياة لتعزيز الصحة العامة؟,نعم، تقدم الشركة استشارات حول الوقاية من الأمراض وتغيير نمط الحياة لتعزيز الصحة العامة.
|
| 91 |
+
هل يمكنني الحصول على دعم لبرامج إدارة الإجهاد؟,نعم، نوفر استشارات في مجالات الصحة العامة وإدارة الإجهاد.
|
| 92 |
+
ما هو عنوان الموقع الإلكتروني للشركة؟,الموقع الإلكتروني هو [www.alshifa-care.com](https://www.google.com/search?q=https://www.alshifa-care.com).
|
| 93 |
+
ما هي وسائل التواصل الاجتماعي الخاصة بالشركة؟,يمكن التواصل مع الشركة عبر تويتر (X)، لينكد إن، فيسبوك، وإنستغرام.
|
| 94 |
+
هل يمكنني تقديم ملاحظاتي أو مشاكل عبر البريد الإلكتروني؟,نعم، يمكنك إرسال استفساراتكم أو مشاكلكم عبر البريد الإلكتروني، وسيقوم فريق الدعم بالرد في أقرب وقت.
|
| 95 |
+
ما هي أنواع الباقات والعضويات التي تقدمونها؟,تقدم الشركة خيارات اشتراك شهرية أو سنوية توفر خصومات إضافية ووصولاً غير محدود لبعض الخدمات.
|
| 96 |
+
هل يمكنني الحصول على مساعدة فورية للدعم الفني؟,نعم، الدردشة المباشرة متوفرة على مدار الساعة (24/7) لتقديم المساعدة الفورية.
|
| 97 |
+
ما هي الميزات المتاحة عند حجز موعد مع طبيب متخصص؟,يمكنك الاطلاع على الملفات الشخصية للأطباء، خبراتهم، وتقييمات المرضى الآخرين.
|
| 98 |
+
ما هو الهدف من السجلات الطبية الإلكترونية الموحدة؟,يُمكّن السجل الأطباء من الوصول إلى السجل الطبي للمريض (بموافقته) لتوفير رعاية أكثر دقة وشمولية.
|
| 99 |
+
هل يمكن للمستخدمين الحصول على دليل شامل للأسئلة الشائعة؟,نعم، يتوفر على الموقع قسم للمساعدة والأسئلة الشائعة يحتوي على إجابات لمعظم الاستفسارات.
|
| 100 |
+
ما هو الغرض من تأسيس شركة الشفاء الرقمية؟,الغرض هو سد الفجوة بين المرضى والأطباء المتخصصين، وتمكين الأفراد من الحصول على استشارات طبية موثوقة ومتابعة مستمرة.
|
| 101 |
+
ماذا تعني كلمة "التعاطف والرعاية" كقيمة أساسية للشركة؟,تعني أن الشركة تتعامل مع كل مريض كفرد فريد وتقدم الرعاية بتعاطف وتفهم، مع الحرص على توفير بيئة داعمة ومريحة.
|
| 102 |
+
هل يمكنني الحصول على استشارة طبية بخصوص الأطفال؟,نعم، لدينا أطباء متخصصون في تخصص الأطفال.
|
| 103 |
+
ما هي المدة التي يمكنني خلالها طلب استرداد المبلغ؟,يمكن طلب استرداد المبلغ خلال 24 ساعة من تقديم الخدمة أو عدم إتمامها.
|
| 104 |
+
هل هناك خصومات عند الاشتراك في باقات طويلة الأمد؟,نعم، خيارات الاشتراك الشهرية أو السنوية توفر خصومات إضافية.
|
| 105 |
+
هل يمكنني الحصول على وصفة طبية إلكترونية؟,نعم، يمكن إرسال الوصفة إلكترونيًا إذا رأى الطبيب ضرورة لذلك.
|
| 106 |
+
ما هي المسؤولية الملقاة على عاتق المستخدم؟,يجب على المستخدم تقديم معلومات طبية وشخصية صحيحة وكاملة ودقيقة أثناء التسجيل وخلال الاستشارات.
|
| 107 |
+
ما هي الخدمات التي تقدمونها للمتابعة بعد العمليات الجراحية؟,نوفر جلسات متابعة مستمرة للحالات بعد العمليات الجراحية.
|
| 108 |
+
هل يتم تقييم أداء الأطباء؟,نعم، يتم تقييم أداء الأطباء بانتظام بناءً على معايير الجودة ورضا المرضى.
|
| 109 |
+
ما هو التزام الشركة تجاه التكنولوجيا؟,تلتزم الشركة بتسخير أحدث التكنولوجيا الرقمية لبناء جسر فعال بين المرضى والخبراء الطبيين.
|
| 110 |
+
ما هي الفائدة من نظام السجلات الطبية الإلكترونية للمرضى؟,يمكّن النظام المريض من الوصول إلى سجله الطبي.
|
| 111 |
+
كيف يمكنني التواصل مع الدعم الفني عبر الهاتف؟,يتوفر فريق دعم مخصص جاهز للإجابة على الاستفسارات التقنية والخدمية خلال ساعات العمل الرسمية.
|
| 112 |
+
هل يمكنني حجز موعد مع أخصائي نفسي؟,نعم، نوفر جلسات استشارية مع أخصائيين نفسيين ومعالجين سلوكيين.
|
| 113 |
+
ما هو الهدف من خدمة تحليل نتائج المختبر؟,الهدف هو توجيه المريض إلى التحاليل اللازمة بناءً على الأعراض أو التاريخ المرضي.
|
| 114 |
+
هل يمكنني طلب الأدوية بدون وصفة من الصيدلية الرقمية؟,نعم، يمكنك طلب الأدوية التي لا تتطلب وصفة طبية مباشرة من الصيدلية الرقمية.
|
| 115 |
+
هل يمكنني حجز استشارة لأحد كبار السن من عائلتي؟,نعم، يمكنك حجز استشارة نيابة عن أحد كبار السن مع مراعاة الحصول على موافقته.
|
| 116 |
+
ما هي فوائد برامج الصحة الوقائية للموظفين؟,البرامج الصحية المخصصة للشركات تهدف إلى تعزيز رفاهية الموظفين وتقليل التغيب عن العمل.
|
| 117 |
+
هل هناك أي محتوى على المنصة محمي بحقوق الملكية الفكرية؟,نعم، جميع المحتويات والمواد المتوفرة على المنصة هي ملك لشركة الشفاء الرقمية ومحمية بموجب قوانين الملكية الفكرية.
|
| 118 |
+
ما هو الدور الأساسي لشركة الشفاء الرقمية؟,دورها هو أن تكون شركة رائدة ومتطورة في مجال الرعاية الصحية عن بُعد.
|
| 119 |
+
هل يمكنني الحصول على استشارات بخصوص الأمراض المزمنة؟,نعم، نوفر جلسات متابعة مستمرة للحالات المزمنة.
|
| 120 |
+
كيف يمكنني العثور على معلومات حول خبرات الأطباء؟,يمكنك الاطلاع على الملفات الشخصية للأطباء، والتي تتضمن خبراتهم.
|
| 121 |
+
هل يتم تحديث سياسة الخصوصية؟,نعم، قد يتم تحديث سياسة الخصوصية من وقت لآخر، وسيتم إخطار المستخ��مين بأي تغييرات جوهرية.
|
| 122 |
+
ما هي الإجراءات الأمنية المطبقة على خوادم وقواعد بيانات الشركة؟,يتم تطبيق إجراءات أمنية صارمة على الخوادم وقواعد البيانات لمنع الوصول غير المصرح به.
|
| 123 |
+
هل يمكنني الحصول على دعم فني خارج ساعات العمل الرسمية؟,قد تتوفر بعض خدمات الدعم الفني أو الدردشة المباشرة خارج ساعات العمل المحددة.
|
| 124 |
+
ما هو دور "الشفافية" كقيمة أساسية؟,تعني بناء الثقة مع المرضى من خلال تقديم معلومات واضحة وصادقة حول الخدمات وتكاليفها، وضمان الحصول على رأي طبي دقيق وموثوق به.
|
| 125 |
+
ما هي التخصصات المتاحة للاستشارات الطبية عبر الفيديو؟,تشمل التخصصات الباطنية، الأطفال، الجلدية، النساء والولادة، العيون، والأنف والأذن والحنجرة.
|
| 126 |
+
كيف يمكنني حجز موعد للكشف الجسدي في عيادتكم؟,يمكنك حجز المواعيد المسبقة لضمان حصولك على الرعاية اللازمة في الوقت المناسب.
|
| 127 |
+
هل يمكنني الحصول على تقييم لنتائج صور الأشعة؟,نعم، تقدم الشركة خدمة مراجعة وتقييم لصور الأشعة من قبل أطباء متخصصين.
|
| 128 |
+
هل يمكنني طلب توصيل أدوية لا تتطلب وصفة طبية؟,نعم، يمكنك طلب الأدوية التي لا تتطلب وصفة طبية مباشرة من الصيدلية الرقمية.
|
| 129 |
+
ما هو الغرض من نظام السجلات الطبية الإلكترونية الموحدة؟,يهدف إلى توفير رعاية أكثر دقة وشمولية عن طريق تمكين الأطباء من الوصول إلى السجل الطبي للمريض.
|
| 130 |
+
هل يمكن أن تساعدوني في فهم حالتي الصحية بشكل أفضل بناءً على نتائج التحاليل؟,نعم، يتم تقديم توصيات بناءً على النتائج ومساعدة المرضى على فهم حالتهم الصحية بشكل أفضل.
|
| 131 |
+
هل يمكنني الحصول على دعم بخصوص مؤشراتي الحيوية؟,نعم، نوفر برامج مخصصة لمتابعة المؤشرات الحيوية (مثل ضغط الدم، السكري) للأشخاص المعرضين للخطر.
|
| 132 |
+
ما هي الفائدة من حجز مواعيد مع متخصصين عبر المنصة؟,تمكنك المنصة من الاطلاع على الملفات الشخصية للأطباء، خبراتهم، وتقييمات المرضى الآخرين لاختيار الأنسب.
|
| 133 |
+
ماذا يحدث إذا كان هناك نزاع قضائي؟,في حالة وجود أي نزاعات، يكون الاختصاص القضائي للمحاكم المختصة في المملكة العربية السعودية.
|
| 134 |
+
هل يمكنني استخدام الدردشة المباشرة لطرح أسئلة حول خدمات الشركة؟,نعم، الدردشة المباشرة متاحة لتقديم المساعدة الفورية والإجابة على الاستفسارات.
|
| 135 |
+
هل يمكنني الحصول على دعم لبرامج التغذية؟,نعم، نقدم استشارات في مجالات الصحة العامة والتغذية.
|
| 136 |
+
ما هو التزام الشركة تجاه جودة الحياة؟,تهدف الشركة إلى إحداث فرق حقيقي وملموس في جودة حياة الأفراد والمجتمعات.
|
| 137 |
+
ما هي أنواع البطاقات البنكية التي تقبلونها للدفع؟,نقبل جميع البطاقات الائتمانية والخصم المباشر الرئيسية (فيزا / ماستركارد / مدى).
|
| 138 |
+
هل يمكنني الحصول على استشارة بخصوص الحالات المزمنة؟,نعم، نوفر جلسات متابعة مستمرة للحالات المزمنة.
|
| 139 |
+
ما هي طرق التواصل الأخرى المتاحة غير الهاتف والبريد الإلكتروني؟,يمكن التواصل عبر الموقع الإلكتروني ووسائل التواصل الاجتماعي مثل تويتر، لينكد إن، وفيسبوك.
|
| 140 |
+
ما هو هدف الشركة في مجال الرعاية الصحية؟,الهدف هو جعل الرعاية الصحية في متناول الجميع، وتقديم حلول مبتكرة تسهل وصول المرضى للخدمات التي يحتاجونها دون عناء.
|
| 141 |
+
هل يمكنني حجز استشارة لأطفالي؟,نعم، يمكنك حجز استشارة نيابة عن أطفالك.
|
| 142 |
+
ما هو نطاق عمل الشركة؟,نطاق العمل يشمل الرعاية الصحية عن بُعد.
|
| 143 |
+
كيف يمكنني التأكد من أن الأطباء مؤهلون؟,جميع الأطباء يخضعون لعملية تد��يق صارمة لضمان أعلى مستويات الكفاءة والاحترافية.
|
| 144 |
+
هل يمكنني الحصول على استشارة بخصوص مشاكل الباطنية؟,نعم، لدينا أطباء باطنية متخصصون.
|
| 145 |
+
ما هي الخيارات المتاحة لطلب الأدوية؟,يمكنك الحصول على الوصفات الطبية الإلكترونية وتوصيل الأدوية للمنزل.
|
| 146 |
+
هل يمكنني الحصول على دعم بخصوص الصحة العامة؟,نعم، نقدم استشارات في مجالات الصحة العامة.
|
| 147 |
+
كيف يتم حماية معلوماتي الشخصية والطبية؟,تستخدم الشركة تقنيات التشفير المتقدمة لحماية جميع الاتصالات والبيانات المخزنة.
|
| 148 |
+
هل يمكنني معرفة تقييمات المرضى الآخرين للأطباء؟,نعم، يمكنك الاطلاع على تقييمات المرضى الآخرين عند اختيار الطبيب.
|
| 149 |
+
هل يمكنني طلب الدعم الفني عن طريق الهاتف؟,نعم، يتوفر فريق دعم مخصص عبر الهاتف خلال ساعات العمل الرسمية.
|
| 150 |
+
هل يمكنني حجز موعد مع أخصائي في اللياقة البدنية؟,نعم، نقدم استشارات في مجالات اللياقة البدنية.
|
| 151 |
+
ما هو هدف الشركة في بناء جسر فعال بين المرضى والخبراء؟,الهدف هو تقديم خدمات طبية موثوقة وآمنة وسهلة الوصول لجميع أفراد المجتمع.
|
| 152 |
+
هل يمكنني الحصول على توصيات بناءً على التحاليل المخبرية؟,نعم، يتم تقديم توصيات بناءً على النتائج ومساعدة المرضى على فهم حالتهم الصحية.
|
| 153 |
+
هل يمكنني الحصول على استشارة بخصوص برامج دعم شاملة للحالات المزمنة؟,نعم، نوفر برامج دعم شاملة للمرضى الذين يعانون من حالات صحية مزمنة.
|
| 154 |
+
هل يمكنني الوصول إلى سجلاتي الطبية من أي مكان؟,نعم، يوفر النظام السجل الطبي الإلكتروني الموحد الذي يمكن الوصول إليه (بموافقتك).
|
| 155 |
+
هل هناك أي برامج توعية صحية تقدمونها؟,نعم، نوفر حملات توعية صحية دورية عبر المنصة حول الأمراض الشائعة وطرق الوقاية منها.
|
| 156 |
+
ما هي أنواع الاستشارات التي تقدمونها للأطباء العامين؟,نقدم استشارات فورية ومجدولة مع أطباء عامين.
|
| 157 |
+
هل يمكنني حجز موعد مع طبيب باطنية؟,نعم، يمكنك حجز موعد مع طبيب متخصص في تخصص الباطنية.
|
| 158 |
+
هل يمكنني الحصول على استشارة عن طريق الهاتف؟,نعم، من بين الخدمات المتاحة التواصل الصوتي والمرئي.
|
| 159 |
+
هل يمكنني حجز موعد عبر الموقع الإلكتروني؟,نعم، يمكن حجز المواعيد المسبقة لضمان حصولك على الرعاية اللازمة.
|
| 160 |
+
هل تقدمون خدمات للعائلات؟,نعم، نقدم خيارات اشتراك متعددة تناسب احتياجات الأفراد والعائلات.
|
| 161 |
+
ما هي المدة المتوقعة للرد على استفسارات البريد الإلكتروني؟,يتم الرد على استفسارات البريد الإلكتروني خلال 24 ساعة عمل.
|
| 162 |
+
ما هي فوائد العضويات السنوية؟,توفر العضويات السنوية خصومات إضافية ووصولاً غير محدود لبعض الخدمات.
|
| 163 |
+
ما هي أهمية "الاحترافية" كقيمة أساسية؟,تعني أن الشركة تضمن أن جميع خدماتها الطبية تُقدم من قبل متخصصين مؤهلين، وتلتزم بأعلى معايير الرعاية الصحية العالمية.
|
| 164 |
+
هل يمكنني طلب الأدوية التي وصفت لي إلكترونياً؟,نعم، يمكنك صرف الوصفات الطبية الإلكترونية التي يكتبها أطباؤنا.
|
| 165 |
+
هل يمكنني حجز استشارة في أي وقت؟,نعم، يمكنك الحصول على استشارات فورية أو مجدولة.
|
| 166 |
+
ما هو التزام الشركة تجاه الشفافية؟,نلتزم بتقديم معلومات واضحة وصادقة حول خدماتنا وتكاليفها.
|
| 167 |
+
هل يمكنني استخدام تطبيق Apple Pay للدفع؟,نعم، يمكنك الدفع السريع والآمن عبر تطبيق Apple Pay.
|
| 168 |
+
ما هي أنواع الاستشارات النفسية التي تقدمونها؟,نقدم جلسات استشارية مع أخصائيين نفسيين ومعالجين سلوكيين.
|
| 169 |
+
هل يمكنني الحصول على استشارة بخصوص مشاكل النساء والولادة؟,نعم، ��وفر استشارات مع أطباء نساء وولادة.
|
| 170 |
+
ما هي شروط الاشتراك في الخدمات؟,باستخدامك لخدمات الشركة، فإنك توافق على الالتزام بالشروط والأحكام.
|
| 171 |
+
هل يمكنني الحصول على استشارة من أخصائي تغذية؟,نعم، نقدم استشارات في مجالات التغذية.
|
| 172 |
+
هل يمكنني الحصول على معلومات حول كيفية الوقاية من الأمراض؟,نعم، تقدم الشركة استشارات حول الوقاية من الأمراض وتغيير نمط الحياة لتعزيز الصحة العامة.
|
| 173 |
+
ما هو الغرض من تقديم استشارات طبية عبر الفيديو؟,الغرض هو توفير رعاية طبية شاملة وعالية الجودة في أي وقت ومن أي مكان.
|
| 174 |
+
هل يمكنني حجز موعد مع أخصائي نفسي عبر الفيديو؟,نعم، يمكنك حجز جلسات استشارية مع أخصائيين نفسيين.
|
| 175 |
+
ما هو هدف الشركة من تقديم خدمات الطب عن بُعد للمؤسسات؟,الهدف هو تقديم حزم رعاية صحية رقمية للشركات لرعاية موظفيهم وتعزيز رفاهيتهم.
|
| 176 |
+
ما هي إجراءات الأمان المتبعة لحماية البيانات؟,يتم تشفير جميع الاتصالات والبيانات باستخدام أحدث بروتوكولات الأمان.
|
| 177 |
+
هل يمكنني الاطلاع على ملفات الأطباء المتوفرين؟,نعم، يمكنك تصفح ملفات الأطباء المتوفرين والاطلاع على تخصصاتهم وخبراتهم.
|
| 178 |
+
هل يمكنني إلغاء الاشتراك بنفسي؟,نعم، يمكنك تسجيل الدخول إلى حسابك وإلغاء الاشتراك من "إدارة الاشتراكات".
|
| 179 |
+
ما هي المعلومات التي يتم جمعها؟,يتم جمع البيانات الشخصية (مثل الاسم والعمر) والمعلومات الصحية (التاريخ المرضي والتشخيصات).
|
| 180 |
+
ما هو الغرض من "التعاطف والرعاية" في تعاملكم مع المرضى؟,التعامل مع كل مريض كفرد فريد وتقديم الرعاية بتعاطف وتفهم، مع توفير بيئة داعمة ومريحة.
|
| 181 |
+
ما هي أنواع الملفات التي يمكن تبادلها خلال الاستشارة؟,يمكن تبادل الملفات والصور لتحسين جودة التشخيص.
|
| 182 |
+
هل يمكنني طلب مساعدة من الدعم الفني عبر الهاتف في أي وقت؟,فريق الدعم المخصص متوفر للإجابة على الاستفسارات التقنية والخدمية خلال ساعات العمل الرسمية.
|
| 183 |
+
ما هي وسائل التواصل الاجتماعي المتاحة لشركة الشفاء الرقمية؟,تويتر (X)، لينكد إن، فيسبوك، وإنستغرام.
|
| 184 |
+
ماذا يحدث إذا واجهت صعوبة في إلغاء الاشتراك؟,يمكنك التواصل مع فريق الدعم الفني لمساعدتك في إتمام عملية الإلغاء.
|
| 185 |
+
ما هو الغرض من "التيسير والسرعة"؟,الهدف هو جعل الرعاية الصحية سهلة ومتاحة للجميع، وتوفير حلولاً سريعة وفعالة للحصول على الاستشارات.
|
| 186 |
+
هل يمكنني الحصول على استشارة بخصوص مشاكل الأذن؟,نعم، لدينا أطباء متخصصون في الأنف والأذن والحنجرة.
|
| 187 |
+
ما هو الالتزام الأساسي لسياسة الخصوصية؟,تتعهد سياسة الخصوصية بجمع البيانات فقط لغرض تقديم الخدمات الطبية وتحسينها، وبموافقة صريحة من المستخدم.
|
| 188 |
+
هل يمكنني طلب الأدوية التي تتطلب وصفة طبية؟,نعم، يمكن صرف الوصفات الطبية الإلكترونية التي يكتبها أطباؤنا.
|
| 189 |
+
هل يتم حفظ السجل الطبي الإلكتروني للمريض؟,نعم، يتم تخزين السجل الطبي للمريض بشكل آمن.
|
| 190 |
+
ما هي القيمة المضافة من استخدام التكنولوجيا في الشركة؟,تسخير أحدث التكنولوجيا الرقمية لبناء جسر فعال بين المرضى والخبراء الطبيين.
|
| 191 |
+
هل يمكنني حجز استشارة للأطفال؟,نعم، يمكنك حجز استشارة نيابة عن الأطفال.
|
| 192 |
+
ما هي فوائد الاستشارات الطبية عبر الفيديو؟,تمكنك من الحصول على استشارات طبية موثوقة ومتابعة مستمرة في أي وقت ومن أي مكان.
|
| 193 |
+
هل يمكنني الدفع عبر المحافظ الرقمية؟,نعم، يمكنك الدفع عبر تطبيق Apple Pay و STC Pay.
|
| 194 |
+
ما هو عنوان البريد الإلكتروني للدعم الفني؟,البريد ا��إلكتروني للدعم هو support@alshifa-care.com.
|
| 195 |
+
ما هي شروط الاستخدام المتعلقة بالملكية الفكرية؟,يُمنع نسخ، تعديل، توزيع، أو إعادة نشر أي من المواد الموجودة على المنصة دون إذن كتابي مسبق.
|
| 196 |
+
هل يمكنني حجز موعد مع أخصائي سلوكي؟,نعم، نقدم جلسات استشارية مع أخصائيين نفسيين ومعالجين سلوكيين.
|
| 197 |
+
ما هي فوائد نظام السجلات الطبية الإلكترونية؟,يقلل من الحاجة لتكرار الفحوصات ويسهل عملية المتابعة.
|
| 198 |
+
هل يمكنني الحصول على دعم بخصوص إدارة الإجهاد؟,نعم، نقدم استشارات في مجالات إدارة الإجهاد.
|
| 199 |
+
ما هي أوقات العمل الرسمية للشركة؟,من الأحد إلى الخميس، من 9 صباحًا حتى 9 مساءً بتوقيت المملكة العربية السعودية (EEST).
|
| 200 |
+
هل يمكنني طلب استشارة بخصوص الأمراض الشائعة؟,نعم، نوفر حملات توعية صحية دورية حول الأمراض الشائعة وطرق الوقاية منها.
|
| 201 |
+
ما هي طريقة تصفح الأطباء المتاحة؟,يمكنك تصفح ملفات الأطباء المتوفرين، والاطلاع على تخصصاتهم، خبراتهم، وتقييمات المرضى الآخرين.
|
| 202 |
+
هل يمكنني الحصول على شرح لتاريخي المرضي؟,نعم، يتم تخزين السجل الطبي للمريض بما في ذلك التشخيصات والأدوية الموصوفة وتاريخ الاستشارات.
|
| 203 |
+
هل يمكنني حجز موعد للكشف الجسدي في عيادتكم في أي وقت؟,يمكنك حجز المواعيد المسبقة لضمان حصولك على الرعاية اللازمة في الوقت المناسب.
|
| 204 |
+
ما هي سياسة الدفع عند استخدام الباقات الكبيرة؟,يمكن الترتيب للدفع عن طريق التحويل البنكي لبعض الخدمات أو الباقات الكبيرة.
|
| 205 |
+
هل يمكنني الحصول على دعم لبرامج متابعة السكري؟,نعم، نوفر برامج مخصصة لمتابعة المؤشرات الحيوية مثل السكري للأشخاص المعرضين للخطر.
|
| 206 |
+
ما هي شروط الاستخدام المتعلقة بالقانون الحاكم؟,تخضع شروط الاستخدام وتُفسر وفقًا لقوانين المملكة العربية السعودية.
|
| 207 |
+
ما هو الغرض من استخدام ملفات تعريف الارتباط؟,الهدف هو تحسين تجربة التصفح، تحليل أداء الموقع، وتخصيص المحتوى.
|
| 208 |
+
هل يمكنني طلب الأدوية التي تتطلب وصفة طبية إلكترونياً؟,نعم، يمكن صرف الوصفات الطبية الإلكترونية التي يكتبها أطباؤنا.
|
| 209 |
+
هل يتم تخزين بياناتي بشكل آمن؟,نعم، يتم تطبيق إجراءات أمنية صارمة على خوادمنا وقواعد بياناتنا لمنع الوصول غير المصرح به.
|
| 210 |
+
ما هو التزام الشركة تجاه خصوصية المريض؟,خصوصية المريض هي على رأس أولوياتنا، ونلتزم بأقصى درجات السرية.
|
| 211 |
+
ما هي أنواع الخدمات التي يمكن أن أحصل عليها للرعاية النفسية؟,يمكنك الحصول على جلسات استشارية مع أخصائيين نفسيين ومعالجين سلوكيين.
|
| 212 |
+
ما هو نطاق التخصصات المتاحة للاستشارات؟,لدينا أطباء متخصصون في مختلف التخصصات مثل الباطنية، الأطفال، الجلدية، النساء والولادة، العيون، والأنف والأذن والحنجرة.
|
| 213 |
+
هل يمكنني الحصول على مساعدة فورية للدعم الفني؟,نعم، الدردشة المباشرة متوفرة على مدار الساعة (24/7) على موقعنا الإلكتروني.
|
| 214 |
+
ما هي أنواع الاستشارات التي تقدمونها للأطفال؟,نقدم استشارات مع أطباء متخصصين في تخصص الأطفال.
|
| 215 |
+
ما هو التزام الشركة تجاه جودة الرعاية؟,نلتزم بتقديم أعلى معايير الرعاية الصحية وفقًا لأفضل الممارسات العالمية.
|
| 216 |
+
ما هي فوائد برامج الصحة الوقائية؟,تساعد على الوقاية من الأمراض وتغيير نمط الحياة لتعزيز الصحة العامة.
|
| 217 |
+
ما هي شروط الاستخدام المتعلقة بالطوارئ الطبية؟,تُخصص خدماتنا للحالات غير الطارئة فقط، ويجب البحث عن رعاية طبية فورية في حالة الطوارئ.
|
| 218 |
+
هل يمكنني استخدام خدمة الصيدلة لتوصيل الأدوية الموصوفة لي؟,نعم، يمكنك صرف الوصفات الطبية الإلكترونية وتوصيل الأدوية إليك
|
| 219 |
+
هل الصيدلية الإلكترونية توفر جميع أنواع الأدوية؟,تسعى صيدليتنا لتوفير مجموعة واسعة من الأدوية والمستلزمات الطبية، بما في ذلك الأدوية الموصوفة وغير الموصوفة.
|
| 220 |
+
ما هي المدة الزمنية لتوصيل الأدوية؟,تختلف مدة التوصيل حسب الموقع الجغرافي، ولكننا نسعى لتوصيل الأدوية في أقرب وقت ممكن.
|
| 221 |
+
هل تتوفر خدمة توصيل الأدوية في جميع مناطق المملكة؟,نعمل على توسيع نطاق تغطية خدمة التوصيل لتشمل معظم مناطق المملكة العربية السعودية.
|
| 222 |
+
هل يمكنني استلام الأدوية من الصيدلية بدلاً من التوصيل؟,حالياً، الخدمة الأساسية هي التوصيل المنزلي، ولكن يمكنك الاستفسار من فريق الدعم عن خيارات أخرى إن وجدت.
|
| 223 |
+
هل يمكنني الحصول على استشارة لمتابعة حالة مزمنة؟,نعم، نقدم خدمات متابعة للحالات المزمنة لضمان استمرارية الرعاية الصحية.
|
| 224 |
+
هل يمكنني حجز استشارة لأحد أفراد أسرتي؟,نعم، يمكنك حجز استشارة لأفراد عائلتك، مع ضرورة تقديم معلوماتهم الطبية الصحيحة.
|
| 225 |
+
هل تقدمون استشارات للأطفال؟,نعم، لدينا أطباء متخصصون في طب الأطفال لتقديم الاستشارات اللازمة.
|
| 226 |
+
هل تتوفر استشارات الرعاية النفسية لجميع الأعمار؟,نعم، نقدم استشارات نفسية لمختلف الفئات العمرية بما في ذلك الأطفال، المراهقين، والبالغين.
|
| 227 |
+
ما هي سياسة الخصوصية الخاصة بالبيانات الطبية؟,يتم جمع البيانات الشخصية فقط لتقديم الخدمة، ولا يتم مشاركتها مع أي طرف ثالث دون إذن مسبق، ويتم تشفير جميع الاتصالات والبيانات.
|
| 228 |
+
هل يمكنني طلب نسخة من سجلي الطبي عبر المنصة؟,نعم، يمكنك طلب الوصول إلى سجلك الطبي المحفوظ لدينا وفقًا لسياسة الخصوصية وشروط الاستخدام.
|
| 229 |
+
هل الخدمات متوفرة خارج المملكة العربية السعودية؟,حالياً، نركز على خدمة العملاء داخل المملكة العربية السعودية، ولكننا ندرس التوسع المستقبلي.
|
| 230 |
+
هل يمكنني تغيير موعد الاستشارة المحجوز؟,نعم، يمكنك تغيير موعد الاستشارة من خلال حسابك أو بالتواصل مع الدعم الفني، مع مراعاة سياسة الإلغاء والتعديل.
|
| 231 |
+
هل يمكن إلغاء الاستشارة واسترداد المبلغ؟,يمكن إلغاء الاستشارة وطلب استرداد المبلغ إذا تم الإلغاء خلال الفترة المحددة في شروط الخدمة.
|
| 232 |
+
هل أحتاج إلى دفع رسوم إضافية على الاستشارة؟,الرسوم موضحة ضمن تفاصيل الباقة أو الخدمة المختارة. قد تفرض رسوم إضافية لخدمات معينة يتم توضيحها مسبقاً.
|
| 233 |
+
هل توفرون خدمة عملاء باللغة الإنجليزية؟,نعم، يتوفر لدينا دعم عملاء يتحدث اللغة الإنجليزية بالإضافة إلى اللغة العربية.
|
| 234 |
+
ما هي الخطوات المتبعة عند الشعور بتحسن بعد الاستشارة؟,ننصح بمتابعة خطة العلاج الموصوفة، وفي حال الحاجة، يمكنك حجز استشارة متابعة مع نفس الطبيب.
|
| 235 |
+
هل يمكنني تقييم الطبيب بعد الاستشارة؟,نعم، نشجع المستخدمين على تقييم الأطباء بعد كل استشارة للمساهمة في تحسين جودة الخدمة.
|
| 236 |
+
هل يمكنني تقديم شكوى في حال عدم رضائي عن الخدمة؟,نعم، يمكنك تقديم شكوى عبر قنوات الدعم المتاحة، وسنتعامل معها بجدية واهتمام.
|
| 237 |
+
هل تتوفر باقات اشتراك شهرية أو سنوية؟,نعم، نوفر باقات اشتراك شهرية وسنوية لتلبية الاحتياجات المختلفة، مع مزايا وخصومات خاصة.
|
| 238 |
+
هل هناك فترة تجريبية مجانية للخدمات؟,حالياً لا توجد فترة تجريبية مجانية، ولكن قد نقدم عروضًا خاصة من وقت لآخر.
|
| 239 |
+
هل توفرون خدمة ترجمة فورية أثناء الاستشارة؟,لا نقدم خدمة ترجمة فورية حاليًا، ولكن يمكن اختيار أطباء يتحدثون لغات متعددة.
|
| 240 |
+
هل يتم تسجيل الاستشارات لأغراض المراجعة؟,يتم تسجيل بعض الاستشارات لأغراض ضمان الجودة والتدريب، مع الحفاظ على السرية التامة وبعد موافقتك الصريحة.
|
| 241 |
+
هل يمكن للطبيب طلب فحوصات مخبرية؟,نعم، يمكن للطبيب طلب فحوصات مخبرية وإرسال طلب التحويل إلكترونياً.
|
| 242 |
+
هل يمكنني تحميل نتائج الفحوصات الطبية لمراجعتها؟,نعم، يمكنك تحميل نتائج الفحوصات المخبرية والأشعة ليقوم الطبيب بمراجعتها وتقديم رأيه.
|
| 243 |
+
ما هي الإجراءات المتبعة في حال انقطاع الاتصال أثناء الاستشارة؟,في حال انقطاع الاتصال، سيحاول الطبيب أو فريق الدعم إعادة الاتصال بك لإكمال الاستشارة.
|
| 244 |
+
هل يمكنني تغيير كلمة المرور لحسابي؟,نعم، يمكنك تغيير كلمة المرور الخاصة بحسابك من خلال إعدادات الملف الشخصي.
|
| 245 |
+
ماذا أفعل إذا نسيت كلمة المرور الخاصة بي؟,"يمكنك استخدام خيار ""نسيت كلمة المرور"" على صفحة تسجيل الدخول لإعادة تعيينها."
|
| 246 |
+
هل أحتاج إلى تقديم هوية للتحقق من شخصيتي؟,قد يُطلب منك تقديم إثبات هوية عند التسجيل أو في حالات معينة لضمان سلامة بياناتك وتوافقها مع اللوائح.
|
| 247 |
+
هل يمكن للأطباء تقديم شهادات مرضية إلكترونية؟,نعم، في بعض الحالات التي تستدعي ذلك، يمكن للطبيب إصدار شهادة مرضية إلكترونية معتمدة.
|
| 248 |
+
هل خدمة الرعاية النفسية تشمل جلسات علاج سلوكي معرفي (CBT)؟,نعم، يقدم أخصائيونا النفسيون جلسات علاج سلوكي معرفي وتقنيات أخرى للعلاج النفسي.
|
| 249 |
+
هل يمكنني الحصول على وصفة طبية قابلة للتجديد؟,يعتمد ذلك على تقييم الطبيب ونوع الدواء، حيث يمكن إصدار وصفات قابلة للتجديد لبعض الحالات.
|
| 250 |
+
هل توفرون تقارير طبية شاملة بعد الاستشارة؟,نعم، يمكنك الحصول على ملخص للاستشارة يتضمن التشخيص، خطة العلاج، والأدوية الموصوفة.
|
| 251 |
+
هل هناك حدود لعدد الاستشارات التي يمكنني الحصول عليها؟,يعتمد ذلك على الباقة التي اشتركت بها. بعض الباقات توفر عددًا محدودًا، بينما تتيح أخرى استشارات غير محدودة.
|
| 252 |
+
هل يمكنني الحصول على رأي طبي ثانٍ؟,نعم، يمكنك حجز استشارة مع طبيب آخر للحصول على رأي طبي ثانٍ، وذلك ضمن الخدمات المتاحة.
|
| 253 |
+
هل تتوفر خدمات طب الأسنان عن بعد؟,حاليًا لا نقدم خدمات طب الأسنان عن بعد، ولكننا نسعى لتوسيع نطاق التخصصات مستقبلاً.
|
| 254 |
+
هل يمكنني استشارة طبيب أخصائي دون تحويل من طبيب عام؟,نعم، يمكنك حجز موعد مباشر مع الطبيب الأخصائي الذي تختاره.
|
| 255 |
+
هل يتم تتبع الأدوية بعد شحنها؟,نعم، نوفر خدمة تتبع الشحنات للأدوية لضمان وصولها بأمان وفي الوقت المحدد.
|
| 256 |
+
هل تتوفر خدمة الاستشارات الصوتية فقط بدون فيديو؟,نعم، بعض الأطباء قد يقدمون خيار الاستشارة الصوتية فقط، وذلك يعتمد على تفضيل الطبيب ونوع الحالة.
|
| 257 |
+
كيف يمكنني التأكد من كفاءة الأطباء؟,يتم اختيار الأطباء بعناية فائقة بناءً على مؤهلاتهم، خبرتهم، وتراخيصهم المهنية، ويتم تقييم أدائهم باستمرار.
|
| 258 |
+
هل يمكن للأطباء وصف أدوية نفسية؟,يمكن للأطباء النفسيين المرخصين وصف الأدوية النفسية إذا رأوا ضرورة لذلك، وفقًا للوائح المحلية.
|
| 259 |
+
ما هي ساعات استجابة الدعم الفني عبر البريد الإلكتروني؟,نهدف إلى الرد على جميع الاستفسارات عبر البريد الإلكتروني خلال 24 ساعة عمل.
|
| 260 |
+
هل يمكنني إلغاء موعد الصيدلية والتوصيل؟,نعم، يمكن إلغاء الطلب قبل عملية الشحن، يرجى التواصل مع الدعم الفني في أقرب وقت مم��ن.
|
| 261 |
+
هل هناك رسوم خفية على الخدمات؟,لا، جميع الرسوم والتكاليف موضحة بشفافية تامة قبل إتمام عملية الدفع.
|
| 262 |
+
هل يمكنني الاحتفاظ بسجلي الطبي على جهازي الخاص؟,يمكنك تنزيل نسخة من ملخص الاستشارات والتقارير المتاحة لك على المنصة.
|
| 263 |
+
هل تتوافق المنصة مع متصفحات الإنترنت المختلفة؟,نعم، تم تصميم المنصة لتكون متوافقة مع معظم متصفحات الإنترنت الشائعة (كروم، فايرفوكس، سفاري، إيدج).
|
data/raw_company_info/info.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# شركة الشفاء الرقمية للرعاية الصحية
|
| 2 |
+
|
| 3 |
+
## 🌟 نبذة عن الشركة
|
| 4 |
+
|
| 5 |
+
شركة الشفاء الرقمية هي شركة رائدة ومتطورة في مجال **الرعاية الصحية عن بُعد (Telehealth)**، تأسست على مبدأ توفير رعاية طبية شاملة وعالية الجودة عبر الإنترنت. نهدف إلى سد الفجوة بين المرضى والأطباء المتخصصين، وتمكين الأفراد من الحصول على استشارات طبية موثوقة، متابعة مستمرة، وخدمات صحية متكاملة في أي وقت ومن أي مكان، وذلك بفضل أحدث التقنيات الرقمية والكوادر الطبية المتميزة. نحن نؤمن بأن الرعاية الصحية يجب أن تكون في متناول الجميع، ولذلك نعمل بلا كلل لتقديم حلول مبتكرة تسهل وصول المرضى للخدمات التي يحتاجونها دون عناء التنقل أو الانتظار.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 🎯 الرؤية
|
| 10 |
+
|
| 11 |
+
أن نكون **الخيار الأول والموثوق به عالميًا** في مجال الرعاية الصحية الرقمية، وأن نُحدِث فرقًا حقيقيًا وملموسًا في جودة حياة الأفراد والمجتمعات من خلال تقديم حلول صحية مبتكرة ومستدامة، تُلبي احتياجات الحاضر وتتطلع لمستقبل صحي أفضل للجميع. نطمح إلى بناء مجتمع صحي واعي، حيث تكون الوقاية والعلاج السريع جزءًا لا يتجزأ من الثقافة العامة.
|
| 12 |
+
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
## 📌 الرسالة
|
| 16 |
+
|
| 17 |
+
تقديم خدمات طبية **موثوقة، آمنة، وسهلة الوصول** لجميع أفراد المجتمع، مع التركيز على الكفاءة المهنية والشفافية التامة، وذلك من خلال تسخير **أحدث التكنولوجيا الرقمية** لبناء جسر فعال بين المرضى والخبراء الطبيين. نلتزم بتقديم تجربة رعاية صحية سلسة وشاملة، تراعي احتياجات كل فرد وتضمن خصوصيته وأمن معلوماته.
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## 💡 القيم الأساسية
|
| 22 |
+
|
| 23 |
+
في شركة الشفاء الرقمية، تتجسد قيمنا في كل جانب من جوانب عملنا، وهي تشكل حجر الزاوية الذي نبني عليه علاقاتنا مع مرضانا وشركائنا:
|
| 24 |
+
|
| 25 |
+
* **السرية والخصوصية (Confidentiality & Privacy):** نلتزم بأقصى درجات السرية في التعامل مع بيانات المرضى ومعلوماتهم الصحية، ونطبق بروتوكولات أمان صارمة لضمان حماية معلوماتهم الشخصية والطبية من أي وصول غير مصرح به. خصوصية المريض هي على رأس أولوياتنا.
|
| 26 |
+
* **الجودة والاحترافية (Quality & Professionalism):** نضمن أن جميع خدماتنا الطبية تُقدم من قبل أطباء ومتخصصين مؤهلين تأهيلاً عاليًا، ويتمتعون بخبرة واسعة في مجالاتهم. نسعى دائمًا للتميز وتقديم أعلى معايير الرعاية الصحية وفقًا لأفضل الممارسات العالمية.
|
| 27 |
+
* **التيسير والسرعة (Accessibility & Speed):** نهدف إلى جعل الرعاية الصحية سهلة ومتاحة للجميع، بغض النظر عن موقعهم الجغرافي. نوفر حلولاً سريعة وفعالة للحصول على الاستشارات والمواعيد، مما يوفر الوقت والجهد على المرضى.
|
| 28 |
+
* **الموثوقية والشفافية (Reliability & Transparency):** نبني الثقة مع مرضانا من خلال تقديم معلومات واضحة وصادقة حول خدماتنا وتكاليفها، ونحرص على بناء علاقات طويلة الأمد قائمة على المصداقية والنزاهة. نضمن الحصول على رأي طبي دقيق وموثوق به.
|
| 29 |
+
* **التعاطف والرعاية (Empathy & Care):** نتعامل مع كل مريض كفرد فريد له احتياجاته الخاصة، ونقدم الرعاية بتعاطف وتفهم، مع الحرص على توفير بيئة داعمة ومريحة تساهم في شفائهم وراحتهم النفسية.
|
| 30 |
+
* **الابتكار والتطوير المستمر (Innovation & Continuous Improvement):** نتبنى أحدث التقنيات ونبحث دائمًا عن طرق جديدة ومبتكرة لتحسين خدماتنا وتوسيع نطاقها، لضمان مواكبة التطورات في مجال الرعاية الصحية الرقمية وتلبية الاحتياجات المتغيرة لمرضانا.
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## 🩺 الخدمات التي نقدمها
|
| 35 |
+
|
| 36 |
+
نقدم في شركة الشفاء الرقمية مجموعة واسعة ومتكاملة من الخدمات الصحية الرقمية التي تلبي مختلف احتياجات الرعاية:
|
| 37 |
+
|
| 38 |
+
* **الاستشارات الطبية عبر الفيديو (Video Consultations):**
|
| 39 |
+
* استشارات فورية ومجدولة مع أطباء عامين ومتخصصين في مختلف التخصصات (الباطنية، الأطفال، الجلدية، النساء والولادة، العيون، الأنف والأذن والحنجرة، وغيرها).
|
| 40 |
+
* إمكانية التواصل الصوتي والمرئي الآمن والمشفر، مع خاصية تبادل الملفات والصور لتحسين جودة التشخيص.
|
| 41 |
+
* جلسات متابعة مستمرة للحالات المزمنة أو بعد العمليات الجراحية.
|
| 42 |
+
* **الكشف الجسدي في العيادة (In-clinic Physical Examination):**
|
| 43 |
+
* تقديم خدمات الكشف الجسدي في عيادتنا الخاصة في جميع التخصصات.
|
| 44 |
+
* إمكانية حجز المواعيد المسبقة لضمان حصولك على الرعاية اللازمة في الوقت المناسب.
|
| 45 |
+
* **مراجعة التقارير الطبية والتحاليل (Medical Report & Lab Results Review):**
|
| 46 |
+
* تقديم خدمة مراجعة وتقييم للتقارير الطبية السابقة، صور الأشعة، ونتائج التحاليل المخبرية من قبل أطباء متخصصين لتقديم تفسير واضح وشامل.
|
| 47 |
+
* تقديم توصيات بناءً على النتائج ومساعدة المرضى على فهم حالتهم الصحية بشكل أفضل.
|
| 48 |
+
* **خدمات الصيدلة والتوصيل المنزلي (Pharmacy & Home Delivery Services):**
|
| 49 |
+
* صرف الوصفات الطبية الإلكترونية التي يكتبها أطباؤنا.
|
| 50 |
+
* توصيل الأدوية والمستلزمات الطبية إلى باب المنزل بسرعة وأمان، مع ضمان سلامة المنتجات وجودتها.
|
| 51 |
+
* إمكانية طلب الأدوية التي لا تتطلب وصفة طبية مباشرة من الصيدلية الرقمية.
|
| 52 |
+
* **حجز مواعيد مع أطباء متخصصين (Specialist Doctor Appointments):**
|
| 53 |
+
* منصة سهلة الاستخدام لحجز مواعيد مع مجموعة واسعة من الأطباء الاستشاريين في تخصصات نادرة أو دقيقة.
|
| 54 |
+
* إمكانية الاطلاع على الملفات الشخصية للأطباء، خبراتهم، وتقييمات المرضى الآخرين لاختيار الأنسب.
|
| 55 |
+
* **خدمات الرعاية النفسية والدعم الصحي (Mental Health & Wellness Support):**
|
| 56 |
+
* جلسات استشارية مع أخصائيين نفسيين ومعالجين سلوكيين لدعم الصحة النفسية.
|
| 57 |
+
* تقديم استشارات في مجالات الصحة العامة، التغذية، اللياقة البدنية، وإدارة الإجهاد.
|
| 58 |
+
* برامج دعم شاملة للمرضى الذين يعانون من حالات صحية مزمنة.
|
| 59 |
+
* **تحليل نتائج المختبر (Laboratory Results Analysis):**
|
| 60 |
+
* شرح مفصل لنتائج التحاليل المخبرية وربطها بالحالة الصحية العامة للمريض.
|
| 61 |
+
* توجيه المريض إلى التحاليل اللازمة بناءً على الأعراض أو التاريخ المرضي.
|
| 62 |
+
* **السجلات الطبية الإلكترونية الموحدة (Unified Electronic Health Records - EHR):**
|
| 63 |
+
* نظام آمن لتخزين السجل الطبي للمريض، بما في ذلك التشخيصات، الأدوية الموصوفة، نتائج الفحوصات، وتاريخ الاستشارات.
|
| 64 |
+
* يمكّن الأطباء من الوصول إلى السجل الطبي للمريض (بموافقة المريض) لتوفير رعاية أكثر دقة وشمولية.
|
| 65 |
+
* يقلل من الحاجة لتكرار الفحوصات ويسهل عملية المتابعة.
|
| 66 |
+
* **برامج الصحة الوقائية (Preventive Health Programs):**
|
| 67 |
+
* تقديم استشارات حول الوقاية من الأمراض وتغيير نمط الحياة لتعزيز الصحة العامة.
|
| 68 |
+
* حملات توعية صحية دورية عبر المنصة حول الأمراض الشائعة وطرق الوقاية منها.
|
| 69 |
+
* برامج مخصصة لمتابعة المؤشرات الحيوية (مثل ضغط الدم، السكري) للأشخاص المعرضين للخطر.
|
| 70 |
+
* **خدمات الطب عن بُعد للمؤسسات والشركات (Corporate Telehealth Solutions):**
|
| 71 |
+
* تقديم حزم رعاية صحية رقمية للشركات والمؤسسات لرعاية موظفيهم.
|
| 72 |
+
* برامج صحية مخصصة للشركات لتعزيز رفاهية الموظفين وتقليل التغيب عن العمل.
|
| 73 |
+
|
| 74 |
+
---
|
| 75 |
+
|
| 76 |
+
## 📞 معلومات الاتصال
|
| 77 |
+
|
| 78 |
+
نحن هنا لخدمتكم والإجابة على استفساراتكم. يمكنكم التواصل معنا عبر القنوات التالية:
|
| 79 |
+
|
| 80 |
+
* **الهاتف:** 9200-000-000 (متوفر خلال ساعات العمل الرسمية)
|
| 81 |
+
* **البريد الإلكتروني:** support@alshifa-care.com (نعد بالرد على استفساراتكم خلال 24 ساعة عمل)
|
| 82 |
+
* **الموقع الإلكتروني:** [www.alshifa-care.com](https://www.google.com/search?q=https://www.alshifa-care.com) (منصتكم المتكاملة للرعاية الصحية الرقمية)
|
| 83 |
+
* **العنوان:** الرياض، حي الربيع، طريق الثمامة، المملكة العربية السعودية.
|
| 84 |
+
* **وسائل التواصل الاجتماعي:**
|
| 85 |
+
* **تويتر (X):** @AlShifaDigital
|
| 86 |
+
* **لينكد إن (LinkedIn):** AlShifa Digital Healthcare
|
| 87 |
+
* **فيسبوك (Facebook):** AlShifaDigitalCare
|
| 88 |
+
* **إنستغرام (Instagram):** @alshifadigital
|
| 89 |
+
|
| 90 |
+
---
|
| 91 |
+
|
| 92 |
+
## 💳 طرق الدفع والدعم الفني
|
| 93 |
+
|
| 94 |
+
نسعى لتوفير أقصى درجات الراحة لعملائنا، لذا نوفر خيارات دفع متعددة ودعمًا فنيًا متميزًا:
|
| 95 |
+
|
| 96 |
+
* **طرق الدفع:**
|
| 97 |
+
* **البطاقات البنكية:** نقبل جميع البطاقات الائتمانية والخصم المباشر الرئيسية (فيزا / ماستركارد / مدى).
|
| 98 |
+
* **المحافظ الرقمية:** الدفع السريع والآمن عبر تطبيق Apple Pay و STC Pay.
|
| 99 |
+
* **تحويل بنكي:** (خاص لبعض الخدمات أو الباقات الكبيرة، يمكن الترتيب مسبقاً).
|
| 100 |
+
* **الباقات والعضويات:** خيارات اشتراك شهرية أو سنوية توفر خصومات إضافية ووصولاً غير محدود لبعض الخدمات.
|
| 101 |
+
* **الدعم الفني:**
|
| 102 |
+
* **الدردشة المباشرة (Live Chat):** متوفرة على مدار الساعة (24/7) على موقعنا الإلكتروني لتقديم المساعدة الفورية.
|
| 103 |
+
* **الهاتف:** فريق دعم مخصص جاهز للإجابة على استفساراتكم التقنية والخدمية خلال ساعات العمل.
|
| 104 |
+
* **البريد الإلكتروني:** يمكنكم إرسال استفساراتكم أو مشاكلكم وسيقوم فريق الدعم بالرد عليكم في أقرب وقت ممكن.
|
| 105 |
+
* **قسم المساعدة والأسئلة الشائعة:** دليل شامل ومتجدد على موقعنا يحتوي على إجابات لمعظم الاستفسارات الشائعة حول استخدام المنصة والخدمات.
|
| 106 |
+
|
| 107 |
+
---
|
| 108 |
+
|
| 109 |
+
## ❓ الأسئلة الشائعة (FAQ)
|
| 110 |
+
|
| 111 |
+
نقدم لكم إجابات لأكثر الأسئلة شيوعًا لضمان وضوح كامل حول خدماتنا:
|
| 112 |
+
|
| 113 |
+
**س: ما هي المؤهلات التي يتمتع بها الأطباء في شركة الشفاء الرقمية؟**
|
| 114 |
+
ج: جميع أطبائنا حاصلون على شهادات معتمدة ولديهم سنوات من الخبرة في تخصصاتهم. يخضعون لعملية تدقيق صارمة لضمان أعلى مستويات الكفاءة والاحترافية، ويتم تقييم أدائهم بانتظام بناءً على معايير الجودة ورضا المرضى.
|
| 115 |
+
|
| 116 |
+
**س: هل يمكنني استرداد المبلغ إذا لم أكن راضيًا عن الخدمة؟**
|
| 117 |
+
ج: نعم، نسعى دائمًا لرضاكم التام. يمكن طلب استرداد المبلغ خلال **24 ساعة** من تقديم الخدمة أو عدم إتمامها، وذلك وفقًا لسياسة الاسترداد الخاصة بنا، والتي تحدد الشروط والأحكام لكل خدمة. يرجى مراجعة صفحة "شروط الاستخدام" للحصول على تفاصيل كاملة.
|
| 118 |
+
|
| 119 |
+
**س: هل الاستشارات تشمل وصف الأدوية؟**
|
| 120 |
+
ج: نعم، إذا رأى الطبيب ضرورة طبية لذلك، يمكنه وصف الأدوية اللازمة خلال الاستشارة. يتم إرسال الوصفة إلكترونيًا ويمكن توصيلها إليك مباشرة عبر خدمة الصيدلية الإلكترونية لدينا، أو يمكنك الحصول عليها من أي صيدلية أخرى.
|
| 121 |
+
|
| 122 |
+
**س: هل بياناتي محفوظة وآمنة؟**
|
| 123 |
+
ج: بالتأكيد. نحن نلتزم بأعلى معايير الأمان والخصوصية العالمية لحماية بياناتك الشخصية والطبية. نستخدم تقنيات التشفير المتقدمة (SSL/TLS) لحماية جميع الاتصالات والبيانات المخزنة، ونلتزم بجميع اللوائح والقوانين المتعلقة بحماية البيانات الصحية، مثل قانون حماية البيانات العامة (GDPR) ومعايير HIPAA حيثما ينطبق ذلك. يتم الوصول إلى بياناتك من قبل الأطباء المعنيين فقط وبموافقتك الصريحة.
|
| 124 |
+
|
| 125 |
+
**س: هل يمكنني اختيار الطبيب ��لذي أرغب في الاستشارة معه؟**
|
| 126 |
+
ج: نعم، يمكنك تصفح ملفات الأطباء المتوفرين، والاطلاع على تخصصاتهم، خبراتهم، اللغات التي يتحدثونها، وتقييمات المرضى الآخرين، ومن ثم اختيار الطبيب الأنسب لاحتياجاتك.
|
| 127 |
+
|
| 128 |
+
**س: هل يمكنني الحصول على استشارة طبية في حالات الطوارئ؟**
|
| 129 |
+
ج: لا. خدمات شركة الشفاء الرقمية مخصصة للحالات غير الطارئة والاستشارات الروتينية والمتابعة. **يُمنع منعًا باتًا استخدام الخدمة في حالات الطوارئ الطبية.** في حالة وجود أي حالة طارئة، يرجى التوجه فوراً إلى أقرب مستشفى أو الاتصال بالرقم المخصص للطوارئ في بلدك.
|
| 130 |
+
|
| 131 |
+
**س: ما هي اللغات التي يقدم بها الأطباء الاستشارات؟**
|
| 132 |
+
ج: يقدم أطباؤنا الاستشارات باللغة العربية بشكل أساسي، بالإضافة إلى إمكانية توفير استشارات بلغات أخرى مثل الإنجليزية حسب توفر الأطباء المتحدثين بهذه اللغات. يمكنك التحقق من اللغات التي يتحدثها الطبيب في ملفه الشخصي.
|
| 133 |
+
|
| 134 |
+
**س: هل يمكنني حجز استشارة لأحد أفراد عائلتي؟**
|
| 135 |
+
ج: نعم، يمكنك حجز استشارة نيابة عن أحد أفراد عائلتك (مثل الأطفال أو كبار السن) مع مراعاة الحصول على موافقتهم الصريحة (إذا كانوا بالغين وقادرين على اتخاذ القرار)، وتقديم معلوماتهم الطبية بدقة.
|
| 136 |
+
|
| 137 |
+
**س: هل توفرون فحصاً طبياً شاملاً عن بعد؟**
|
| 138 |
+
ج: لا يمكن إجراء فحص جسدي شامل عن بعد. ولكن في عيادتنا الخاصة نوفر **خدمة الكشف الجسدي في كل التخصصات**.
|
| 139 |
+
---
|
| 140 |
+
|
| 141 |
+
## 📜 شروط الاستخدام
|
| 142 |
+
|
| 143 |
+
باستخدامك لخدمات شركة الشفاء الرقمية، فإنك توافق على الالتزام بالشروط والأحكام التالية:
|
| 144 |
+
|
| 145 |
+
* **عدم استخدام الخدمة في حالات الطوارئ:** تُخصص خدماتنا للحالات غير الطارئة فقط. في أي حالة طارئة، يجب البحث عن رعاية طبية فورية من خلال المستشفيات أو خدمات الطوارئ المحلية.
|
| 146 |
+
* **دقة المعلومات المقدمة:** يجب على المستخدم تقديم معلومات طبية وشخصية صحيحة وكاملة ودقيقة أثناء التسجيل وخلال الاستشارات. أي معلومات غير دقيقة قد تؤثر سلبًا على جودة الرعاية المقدمة.
|
| 147 |
+
* **مسؤولية المستخدم:** الشركة غير مسؤولة عن أي سوء استخدام للخدمة، أو عن أي نتائج سلبية تنجم عن عدم اتباع إرشادات الطبيب أو تقديم معلومات خاطئة.
|
| 148 |
+
* **الملكية الفكرية:** جميع المحتويات والمواد المتوفرة على المنصة (بما في ذلك النصوص، الرسومات، الشعارات، الأيقونات، الصور، ومقاطع الفيديو) هي ملك لشركة الشفاء الرقمية ومحمية بموجب قوانين الملكية الفكرية. يُمنع منعًا باتًا نسخ، تعديل، توزيع، أو إعادة نشر أي من هذه المواد دون إذن كتابي مسبق.
|
| 149 |
+
* **التعديلات على الشروط:** تحتفظ الشركة بالحق في تعديل شروط الاستخدام هذه في أي وقت دون إشعار مسبق. يُعد استمرارك في استخدام الخدمة بعد نشر التعديلات موافقة منك على الشروط المعدلة.
|
| 150 |
+
* **القانون الحاكم:** تخضع شروط الاستخدام هذه وتفسر وفقًا لقوانين المملكة العربية السعودية. في حالة وجود أي نزاعات، يكون الاختصاص القضائي للمحاكم المختصة في المملكة العربية السعودية.
|
| 151 |
+
|
| 152 |
+
---
|
| 153 |
+
|
| 154 |
+
## 🔒 سياسة الخصوصية
|
| 155 |
+
|
| 156 |
+
نحن في شركة الشفاء الرقمية نولي أهمية قصوى لخصوصية بياناتك وحمايتها. تتعهد سياستنا للخصوصية بما يلي:
|
| 157 |
+
|
| 158 |
+
* **جمع البيانات:** يتم جمع البيانات الشخصية (مثل الاسم، العمر، الجنس، معلومات الاتصال) والمعلومات الصحية (التاريخ المرضي، التشخيصات، الأدوية، نتائج الفحوصات) فقط لغرض تقديم الخدمات الطبية وتحسينها، وبموافقة صريحة من المستخدم.
|
| 159 |
+
* **استخدام البيانات:** تُستخدم البيانات لتقديم الاستشارات الطبية، إدارة المواعيد، معالجة الدفعات، وتحسين تجربة المستخدم على المنصة. قد تُستخدم البيانات المجمعة (التي لا تحدد هوية فردية) لأغراض البحث والإحصاءات لتحسين الخدمات الصحية بشكل عام.
|
| 160 |
+
* **مشاركة البيانات:** لا يتم مشاركة البيانات الشخصية أو الصحية مع أي طرف ثالث (بما في ذلك الشركاء التجاريين أو المعلنين) دون إذن مسبق وصريح من المستخدم، إلا في الحالات التي يتطلبها القانون أو بأمر قضائي.
|
| 161 |
+
* **أمن البيانات:** يتم تشفير جميع الاتصالات والبيانات باستخدام أحدث بروتوكولات الأمان (مثل AES-256 و TLS 1.2) لحماية المعلومات أثناء النقل والتخزين. يتم تطبيق إجراءات أمنية صارمة على خوادمنا وقواعد بياناتنا لمنع الوصول غير المصرح به.
|
| 162 |
+
* **حقوق المستخدم:** يحق للمستخدم الوصول إلى بياناته الشخصية، وتعديلها، أو طلب حذفها (مع مراعاة المتطلبات القانونية للاحتفاظ بالسجلات الطبية) في أي وقت. كما يحق للمستخدم سحب موافقته على معالجة البيانات، شريطة ألا يتعارض ذلك مع الالتزامات القانونية.
|
| 163 |
+
* **ملفات تعريف الارتباط (Cookies):** نستخدم ملفات تعريف الارتباط لتحسين تجربة التصفح، تحليل أداء الموقع، وتخصيص المحتوى. يمكن للمستخدمين التحكم في إعدادات ملفات تعريف الارتباط من خلال متصفحاتهم.
|
| 164 |
+
* **التحديثات على سياسة الخصوصية:** قد نقوم بتحديث سياسة الخصوصية هذه من وقت لآخر. سيتم إخطار المستخدمين بأي تغييرات جوهرية من خلال الموقع الإلكتروني أو البريد الإلكتروني.
|
| 165 |
+
|
| 166 |
+
---
|
| 167 |
+
|
| 168 |
+
## ⏰ أوقات العمل والعطل الرسمية
|
| 169 |
+
|
| 170 |
+
نحن ملتزمون بتوفير خدماتنا لكم بأقصى درجات المرونة، مع مراعاة أوقات الراحة اللازمة لفريقنا:
|
| 171 |
+
|
| 172 |
+
* **أيام العمل:** من الأحد إلى الخميس.
|
| 173 |
+
* **الساعات:** 9 صباحًا حتى 9 مساءً بتوقيت المملكة العربية السعودية (EEST).
|
| 174 |
+
* **العطلات الأسبوعية:** الجمعة والسبت.
|
| 175 |
+
* **العطل الرسمية:** بالإضافة إلى العطلات الأسبوعية، تكون الشركة مغلقة خلال الأعياد الرسمية في المملكة العربية السعودية (مثل عيدي الفطر والأضحى واليوم الوطني). سيتم الإعلان عن أي تغييرات في أوقات العمل خلال العطلات مسبقًا عبر الموقع ووسائل التواصل الاجتماعي.
|
| 176 |
+
|
| 177 |
+
**ملاحظة:** قد تتوفر بعض خدمات الدعم الفني أو الدردشة المباشرة خارج ساعات العمل المحددة، يرجى مراجعة قسم الدعم الفني للحصول على التفاصيل.
|
| 178 |
+
|
| 179 |
+
---
|
| 180 |
+
|
| 181 |
+
## 📝 كيفية الاشتراك أو إلغاء الخدمة
|
| 182 |
+
|
| 183 |
+
نسعى لجعل عملية الاشتراك وإلغاء الخدمة بسيطة وواضحة:
|
| 184 |
+
|
| 185 |
+
### للاشتراك في خدماتنا:
|
| 186 |
+
|
| 187 |
+
1. **إنشاء حساب:** قم بزيارة موقعنا الإلكتروني [www.alshifa-care.com](https://www.google.com/search?q=https://www.alshifa-care.com) وانقر على "تسجيل" أو "إنشاء حساب". اتبع الخطوات لإدخال معلوماتك الأساسية وإنشاء ملفك الشخصي.
|
| 188 |
+
2. **اختيار الباقة:** تصفح باقات الخدمات المتاحة لدينا. نقدم خيارات متعددة تناسب احتياجات الأفراد والعائلات، بما في ذلك الاستشارات الفردية، الباقات الشهرية، والعضويات السنوية التي توفر خصومات ومزايا إضافية.
|
| 189 |
+
3. **إدخال معلومات الدفع:** بعد اختيار الباقة، سيتم توجيهك إلى صفحة الدفع الآمنة. أدخل معلومات بطاقتك البنكية أو اختر طريقة الدفع المفضلة لديك لإتمام عملية الاشتراك. ستتلقى تأكيدًا عبر البريد الإلكتروني بمجرد اكتمال الاشتراك.
|
| 190 |
+
4. **بدء الاستفادة:** بمجرد الاشتراك، يمكنك البدء في حجز المواعيد، طلب الاستشارات، والوصول إلى جميع خدماتنا بكل سهولة.
|
| 191 |
+
|
| 192 |
+
### لإلغاء الاشتراك أو الخدمة:
|
| 193 |
+
|
| 194 |
+
* **عبر حسابك:** قم بتسجيل الدخول إلى حسابك على موقعنا الإلكتروني. ثم توجه إلى "إعدادات الحساب" أو "إدارة الاشتراكات" (قد تختلف التسمية قليلاً حسب التحديثات). اختر "إلغاء الاشتراك" واتبع التعليمات. سيتم تفعيل الإلغاء فورًا أو في نهاية فترة الاشتراك الحالية حسب نوع الباقة.
|
| 195 |
+
* **عبر التواصل مع الدعم الفني:** إذا واجهت أي صعوبة في إلغاء الاشتراك بنفسك، يمكنك التواصل مع فريق الدعم الفني لدينا عبر الهاتف، البريد الإلكتروني، أو الدردشة المباشرة. سيقوم فريقنا بمساعدتك في إتمام عملية الإلغاء وتأكيدها لك.
|
| 196 |
+
|
| 197 |
+
**ملاحظة:** يرجى مراجعة شروط الاشتراك في كل باقة، حيث قد تختلف سياسات الإلغاء والاسترداد بناءً على نوع الخدمة أو مدة الاشتراك.
|
requirements.txt
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==5.41.1
|
| 2 |
+
huggingface_hub==0.34.3
|
| 3 |
+
langchain==0.3.27
|
| 4 |
+
langchain_community==0.3.27
|
| 5 |
+
langchain_huggingface==0.3.1
|
| 6 |
+
langchain_openai==0.3.28
|
| 7 |
+
openai==1.99.2
|
| 8 |
+
pandas==2.3.1
|
| 9 |
+
pydantic==2.11.7
|
| 10 |
+
python-dotenv==1.1.1
|
| 11 |
+
pytz==2025.2
|
| 12 |
+
Requests==2.32.4
|
src/__init__.py
ADDED
|
File without changes
|
src/agent.py
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Al Shifa Digital Healthcare - Medical Chatbot Agent
|
| 3 |
+
|
| 4 |
+
This module implements the core agent functionality for the Al Shifa Digital Healthcare
|
| 5 |
+
medical chatbot system. It provides comprehensive medical assistance, appointment booking,
|
| 6 |
+
and company information services with robust error handling and bilingual support.
|
| 7 |
+
|
| 8 |
+
Author: Al Shifa Digital Healthcare Team
|
| 9 |
+
Version: 1.0.0
|
| 10 |
+
License: Proprietary
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import logging
|
| 14 |
+
import traceback
|
| 15 |
+
from typing import Any, AsyncGenerator
|
| 16 |
+
import asyncio
|
| 17 |
+
import requests
|
| 18 |
+
from langchain.agents import create_openai_tools_agent, AgentExecutor
|
| 19 |
+
from langchain.memory import ConversationBufferWindowMemory
|
| 20 |
+
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
|
| 21 |
+
from langchain.schema import OutputParserException
|
| 22 |
+
from langchain.callbacks.base import BaseCallbackHandler
|
| 23 |
+
from openai import RateLimitError, APIError
|
| 24 |
+
|
| 25 |
+
from config import LLM
|
| 26 |
+
from tools import (
|
| 27 |
+
retriever_tool,
|
| 28 |
+
websearch_tool,
|
| 29 |
+
get_current_datetime_tool,
|
| 30 |
+
book_consultation_tool
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# Configure logging
|
| 34 |
+
logging.basicConfig(
|
| 35 |
+
level=logging.INFO,
|
| 36 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
| 37 |
+
handlers=[
|
| 38 |
+
logging.FileHandler('agent.log'),
|
| 39 |
+
logging.StreamHandler()
|
| 40 |
+
]
|
| 41 |
+
)
|
| 42 |
+
logger = logging.getLogger(__name__)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
# ============================================================================
|
| 46 |
+
# STREAMING CALLBACK HANDLER
|
| 47 |
+
# ============================================================================
|
| 48 |
+
|
| 49 |
+
class StreamingCallbackHandler(BaseCallbackHandler):
|
| 50 |
+
"""Custom callback handler for streaming responses."""
|
| 51 |
+
|
| 52 |
+
def __init__(self):
|
| 53 |
+
self.tokens = []
|
| 54 |
+
self.current_response = ""
|
| 55 |
+
|
| 56 |
+
def on_llm_new_token(self, token: str, **kwargs: Any) -> Any:
|
| 57 |
+
"""Called when a new token is generated."""
|
| 58 |
+
self.tokens.append(token)
|
| 59 |
+
self.current_response += token
|
| 60 |
+
|
| 61 |
+
def get_response(self) -> str:
|
| 62 |
+
"""Get the current response."""
|
| 63 |
+
return self.current_response
|
| 64 |
+
|
| 65 |
+
def reset(self):
|
| 66 |
+
"""Reset the handler for a new response."""
|
| 67 |
+
self.tokens = []
|
| 68 |
+
self.current_response = ""
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# ============================================================================
|
| 72 |
+
# CUSTOM EXCEPTION CLASSES
|
| 73 |
+
# ============================================================================
|
| 74 |
+
|
| 75 |
+
class AgentError(Exception):
|
| 76 |
+
"""Base exception for agent-related errors."""
|
| 77 |
+
pass
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class ToolExecutionError(AgentError):
|
| 81 |
+
"""Exception raised when a tool fails to execute."""
|
| 82 |
+
pass
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
class APIConnectionError(AgentError):
|
| 86 |
+
"""Exception raised when API connections fail."""
|
| 87 |
+
pass
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class ValidationError(AgentError):
|
| 91 |
+
"""Exception raised when input validation fails."""
|
| 92 |
+
pass
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# ============================================================================
|
| 96 |
+
# AGENT CONFIGURATION
|
| 97 |
+
# ============================================================================
|
| 98 |
+
|
| 99 |
+
# Available tools for the agent
|
| 100 |
+
AVAILABLE_TOOLS = [
|
| 101 |
+
retriever_tool,
|
| 102 |
+
websearch_tool,
|
| 103 |
+
get_current_datetime_tool,
|
| 104 |
+
book_consultation_tool
|
| 105 |
+
]
|
| 106 |
+
|
| 107 |
+
# System message template for the agent
|
| 108 |
+
SYSTEM_MESSAGE = """
|
| 109 |
+
You are an advanced medical chatbot for "Al Shifa Digital Healthcare" (شركة الشفاء الرقمية للرعاية الصحية).
|
| 110 |
+
Your name is "Al Shifa Digital Assistant" (روبوت الشفاء الرقمي).
|
| 111 |
+
|
| 112 |
+
**LANGUAGE RULE: Always respond in the SAME language as the user's question (Arabic/English).**
|
| 113 |
+
|
| 114 |
+
**CORE MISSION:**
|
| 115 |
+
Provide accurate, evidence-based medical information and assist with appointment booking while prioritizing patient safety.
|
| 116 |
+
|
| 117 |
+
**WORKING HOURS:**
|
| 118 |
+
أيام العمل من الأحد إلى الخميس، من 9 صباحًا حتى 9 مساءً.
|
| 119 |
+
Working days: Sunday to Thursday, 9 AM to 9 PM.
|
| 120 |
+
|
| 121 |
+
**AVAILABLE TOOLS:**
|
| 122 |
+
1. **book_consultation:** For appointment booking (collect all required info first)
|
| 123 |
+
2. **company_knowledge_tool:** For Al Shifa services, hours, company info
|
| 124 |
+
3. **get_current_datetime:** For current date/time (only reliable source)
|
| 125 |
+
4. **Tavily_Search_Tool:** For medical questions (always use for medical queries)
|
| 126 |
+
|
| 127 |
+
**WORKFLOW:**
|
| 128 |
+
|
| 129 |
+
**Medical Questions:**
|
| 130 |
+
- Start with safety disclaimer
|
| 131 |
+
- Use Tavily_Search_Tool for current, evidence-based information
|
| 132 |
+
- Never diagnose - recommend professional consultation
|
| 133 |
+
- **EMERGENCIES:** Immediately instruct to call 997 (emergency services) and seek immediate medical attention
|
| 134 |
+
|
| 135 |
+
**Appointment Booking:**
|
| 136 |
+
- **MANDATORY VALIDATION:** Before booking any appointment, MUST verify:
|
| 137 |
+
- Date is Sunday through Thursday (not Friday or Saturday)
|
| 138 |
+
- Time is between 9:00 AM and 9:00 PM
|
| 139 |
+
- If user requests invalid day/time, inform them of working hours and ask for alternative
|
| 140 |
+
- Collect: patient_name, age, gender, contact_number, email, reason_for_consultation, preferred_date, preferred_time
|
| 141 |
+
- Use get_current_datetime for "today" requests, then check company hours
|
| 142 |
+
- **Validation Messages:**
|
| 143 |
+
- Arabic: "عذراً، نحن نعمل من الأحد إلى الخميس، من 9 صباحًا حتى 9 مساءً. يرجى اختيار موعد آخر."
|
| 144 |
+
- English: "Sorry, we work Sunday to Thursday, 9 AM to 9 PM. Please choose another time."
|
| 145 |
+
|
| 146 |
+
**Company Questions:**
|
| 147 |
+
Use company_knowledge_tool directly
|
| 148 |
+
|
| 149 |
+
**CRITICAL RULES:**
|
| 150 |
+
- **Safety First:** Medical emergencies → direct to call 997 immediately
|
| 151 |
+
- **No Diagnosis:** Provide information only, not medical diagnoses
|
| 152 |
+
- **Evidence-Based:** Always use Tavily_Search_Tool for medical information
|
| 153 |
+
- **Language Match:** Respond in user's language
|
| 154 |
+
- **Professional Boundaries:** Clearly state limitations when uncertain
|
| 155 |
+
- **Working Hours Enforcement:** Never book appointments outside working days/hours
|
| 156 |
+
|
| 157 |
+
**SAFETY DISCLAIMERS:**
|
| 158 |
+
- Arabic: "تنويه هام: للطوارئ اتصل بـ 997 فوراً. هذه معلومات تعليمية ولا تغني عن استشارة طبيب."
|
| 159 |
+
- English: "Important: For emergencies call 997 immediately. This is educational information, not medical advice."
|
| 160 |
+
|
| 161 |
+
**EMERGENCY PROTOCOL:**
|
| 162 |
+
If user describes emergency symptoms (chest pain, difficulty breathing, severe bleeding, loss of consciousness, etc.),
|
| 163 |
+
immediately respond:
|
| 164 |
+
"هذه حالة طوارئ! اتصل بـ 997 فوراً واطلب المساعدة الطبية العاجلة" /
|
| 165 |
+
"This is an emergency! Call 997 immediately and seek urgent medical help."
|
| 166 |
+
|
| 167 |
+
**Language:**
|
| 168 |
+
|
| 169 |
+
- Respond in the same language as the user's query.
|
| 170 |
+
- الرد بنفس لغة استعلام المستخدم.
|
| 171 |
+
|
| 172 |
+
"""
|
| 173 |
+
|
| 174 |
+
# Create the prompt template
|
| 175 |
+
prompt_template = ChatPromptTemplate.from_messages([
|
| 176 |
+
("system", SYSTEM_MESSAGE),
|
| 177 |
+
MessagesPlaceholder("chat_history"),
|
| 178 |
+
("human", "{input}"),
|
| 179 |
+
MessagesPlaceholder("agent_scratchpad"),
|
| 180 |
+
])
|
| 181 |
+
|
| 182 |
+
# Initialize the agent
|
| 183 |
+
agent = create_openai_tools_agent(
|
| 184 |
+
llm=LLM,
|
| 185 |
+
tools=AVAILABLE_TOOLS,
|
| 186 |
+
prompt=prompt_template,
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
# Create agent executor
|
| 190 |
+
agent_executor = AgentExecutor(
|
| 191 |
+
agent=agent,
|
| 192 |
+
tools=AVAILABLE_TOOLS,
|
| 193 |
+
verbose=True,
|
| 194 |
+
handle_parsing_errors=True,
|
| 195 |
+
max_iterations=10,
|
| 196 |
+
max_execution_time=120, # 2 minutes timeout
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
# Initialize memory
|
| 200 |
+
memory = ConversationBufferWindowMemory(
|
| 201 |
+
memory_key="chat_history",
|
| 202 |
+
return_messages=True,
|
| 203 |
+
max_window_size=10
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
# ============================================================================
|
| 207 |
+
# STREAMING AGENT FUNCTIONS
|
| 208 |
+
# ============================================================================
|
| 209 |
+
|
| 210 |
+
async def run_agent_streaming(user_input: str, max_retries: int = 3) -> AsyncGenerator[str, None]:
|
| 211 |
+
"""
|
| 212 |
+
Run the agent with streaming support and comprehensive error handling.
|
| 213 |
+
|
| 214 |
+
This function processes user input through the agent executor with streaming
|
| 215 |
+
capabilities, robust error handling, and automatic retries for recoverable errors.
|
| 216 |
+
|
| 217 |
+
Args:
|
| 218 |
+
user_input (str): The user's input message to process
|
| 219 |
+
max_retries (int, optional): Maximum number of retries for recoverable errors.
|
| 220 |
+
Defaults to 3.
|
| 221 |
+
|
| 222 |
+
Yields:
|
| 223 |
+
str: Chunks of the agent's response as they are generated
|
| 224 |
+
|
| 225 |
+
Raises:
|
| 226 |
+
None: All exceptions are caught and handled internally
|
| 227 |
+
"""
|
| 228 |
+
# Input validation
|
| 229 |
+
if not user_input or not user_input.strip():
|
| 230 |
+
logger.warning("Empty input received")
|
| 231 |
+
yield "عذراً، لم أتلقَ أي سؤال. يرجى إدخال سؤالك أو طلبك."
|
| 232 |
+
return
|
| 233 |
+
|
| 234 |
+
retry_count = 0
|
| 235 |
+
last_error = None
|
| 236 |
+
|
| 237 |
+
while retry_count <= max_retries:
|
| 238 |
+
try:
|
| 239 |
+
# Load conversation history from memory
|
| 240 |
+
chat_history = memory.load_memory_variables({})["chat_history"]
|
| 241 |
+
|
| 242 |
+
logger.info(f"Processing user input (attempt {retry_count + 1}): {user_input[:50]}...")
|
| 243 |
+
|
| 244 |
+
# Create streaming callback handler
|
| 245 |
+
streaming_handler = StreamingCallbackHandler()
|
| 246 |
+
|
| 247 |
+
# Create a new executor with streaming callback for this request
|
| 248 |
+
streaming_executor = AgentExecutor(
|
| 249 |
+
agent=agent,
|
| 250 |
+
tools=AVAILABLE_TOOLS,
|
| 251 |
+
verbose=True,
|
| 252 |
+
handle_parsing_errors=True,
|
| 253 |
+
max_iterations=10,
|
| 254 |
+
max_execution_time=120,
|
| 255 |
+
callbacks=[streaming_handler]
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
# Run the agent in a separate thread to avoid blocking
|
| 259 |
+
def run_sync():
|
| 260 |
+
return streaming_executor.invoke({
|
| 261 |
+
"input": user_input.strip(),
|
| 262 |
+
"chat_history": chat_history
|
| 263 |
+
})
|
| 264 |
+
|
| 265 |
+
# Execute the agent with streaming
|
| 266 |
+
full_response = ""
|
| 267 |
+
previous_length = 0
|
| 268 |
+
|
| 269 |
+
# Start the agent execution in background
|
| 270 |
+
loop = asyncio.get_event_loop()
|
| 271 |
+
task = loop.run_in_executor(None, run_sync)
|
| 272 |
+
|
| 273 |
+
# Stream the response as it's being generated
|
| 274 |
+
while not task.done():
|
| 275 |
+
current_response = streaming_handler.get_response()
|
| 276 |
+
|
| 277 |
+
# Yield new tokens if available
|
| 278 |
+
if len(current_response) > previous_length:
|
| 279 |
+
new_content = current_response[previous_length:]
|
| 280 |
+
previous_length = len(current_response)
|
| 281 |
+
yield new_content
|
| 282 |
+
|
| 283 |
+
# Small delay to prevent overwhelming the client
|
| 284 |
+
await asyncio.sleep(0.1)
|
| 285 |
+
|
| 286 |
+
# Get the final result
|
| 287 |
+
response = await task
|
| 288 |
+
|
| 289 |
+
# Yield any remaining content
|
| 290 |
+
final_response = streaming_handler.get_response()
|
| 291 |
+
if len(final_response) > previous_length:
|
| 292 |
+
yield final_response[previous_length:]
|
| 293 |
+
|
| 294 |
+
# If no streaming content was captured, yield the full response
|
| 295 |
+
if not final_response and response and "output" in response:
|
| 296 |
+
full_output = response["output"]
|
| 297 |
+
# Simulate streaming by yielding word by word
|
| 298 |
+
words = full_output.split(' ')
|
| 299 |
+
for word in words:
|
| 300 |
+
yield word + ' '
|
| 301 |
+
await asyncio.sleep(0.05)
|
| 302 |
+
|
| 303 |
+
# Validate response structure
|
| 304 |
+
if not response or "output" not in response:
|
| 305 |
+
raise ValidationError("Invalid response format from agent")
|
| 306 |
+
|
| 307 |
+
if not response["output"] or not response["output"].strip():
|
| 308 |
+
raise ValidationError("Empty response from agent")
|
| 309 |
+
|
| 310 |
+
# Save conversation context to memory
|
| 311 |
+
memory.save_context(
|
| 312 |
+
{"input": user_input},
|
| 313 |
+
{"output": response["output"]}
|
| 314 |
+
)
|
| 315 |
+
|
| 316 |
+
logger.info(f"Successfully processed user input: {user_input[:50]}...")
|
| 317 |
+
return
|
| 318 |
+
|
| 319 |
+
except RateLimitError as e:
|
| 320 |
+
retry_count += 1
|
| 321 |
+
last_error = e
|
| 322 |
+
wait_time = min(2 ** retry_count, 60) # Exponential backoff, max 60 seconds
|
| 323 |
+
|
| 324 |
+
logger.warning(
|
| 325 |
+
f"Rate limit exceeded. Retrying in {wait_time} seconds... "
|
| 326 |
+
f"(Attempt {retry_count}/{max_retries})"
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
if retry_count <= max_retries:
|
| 330 |
+
await asyncio.sleep(wait_time)
|
| 331 |
+
continue
|
| 332 |
+
else:
|
| 333 |
+
logger.error("Rate limit exceeded after maximum retries")
|
| 334 |
+
yield "عذراً، النظام مشغول حالياً. يرجى المحاولة مرة أخرى بعد قليل."
|
| 335 |
+
return
|
| 336 |
+
|
| 337 |
+
except APIError as e:
|
| 338 |
+
retry_count += 1
|
| 339 |
+
last_error = e
|
| 340 |
+
logger.error(f"OpenAI API error: {str(e)}")
|
| 341 |
+
|
| 342 |
+
if retry_count <= max_retries:
|
| 343 |
+
await asyncio.sleep(2)
|
| 344 |
+
continue
|
| 345 |
+
else:
|
| 346 |
+
yield "عذراً، حدث خطأ في الاتصال بالخدمة. يرجى المحاولة مرة أخرى لاحقاً."
|
| 347 |
+
return
|
| 348 |
+
|
| 349 |
+
except requests.exceptions.ConnectionError as e:
|
| 350 |
+
retry_count += 1
|
| 351 |
+
last_error = e
|
| 352 |
+
logger.error(f"Network connection error: {str(e)}")
|
| 353 |
+
|
| 354 |
+
if retry_count <= max_retries:
|
| 355 |
+
await asyncio.sleep(3)
|
| 356 |
+
continue
|
| 357 |
+
else:
|
| 358 |
+
yield "عذراً، لا يمكنني الاتصال بالخدمة حالياً. يرجى التحقق من اتصال الإنترنت والمحاولة مرة أخرى."
|
| 359 |
+
return
|
| 360 |
+
|
| 361 |
+
except requests.exceptions.Timeout as e:
|
| 362 |
+
retry_count += 1
|
| 363 |
+
last_error = e
|
| 364 |
+
logger.error(f"Request timeout: {str(e)}")
|
| 365 |
+
|
| 366 |
+
if retry_count <= max_retries:
|
| 367 |
+
await asyncio.sleep(2)
|
| 368 |
+
continue
|
| 369 |
+
else:
|
| 370 |
+
yield "عذراً، استغرق الطلب وقتاً أطول من المتوقع. يرجى المحاولة مرة أخرى."
|
| 371 |
+
return
|
| 372 |
+
|
| 373 |
+
except requests.exceptions.RequestException as e:
|
| 374 |
+
logger.error(f"Request error: {str(e)}")
|
| 375 |
+
yield "عذراً، حدث خطأ في الطلب. يرجى المحاولة مرة أخرى."
|
| 376 |
+
return
|
| 377 |
+
|
| 378 |
+
except OutputParserException as e:
|
| 379 |
+
logger.error(f"Output parsing error: {str(e)}")
|
| 380 |
+
yield "عذراً، حدث خطأ في معالجة الاستجابة. يرجى إعادة صياغة سؤالك والمحاولة مرة أخرى."
|
| 381 |
+
return
|
| 382 |
+
|
| 383 |
+
except ValidationError as e:
|
| 384 |
+
logger.error(f"Validation error: {str(e)}")
|
| 385 |
+
yield "عذراً، حدث خطأ في التحقق من صحة البيانات. يرجى المحاولة مرة أخرى."
|
| 386 |
+
return
|
| 387 |
+
|
| 388 |
+
except ToolExecutionError as e:
|
| 389 |
+
logger.error(f"Tool execution error: {str(e)}")
|
| 390 |
+
yield "عذراً، حدث خطأ أثناء تنفيذ إحدى العمليات. يرجى المحاولة مرة أخرى أو التواصل مع الدعم الفني."
|
| 391 |
+
return
|
| 392 |
+
|
| 393 |
+
except Exception as e:
|
| 394 |
+
logger.error(f"Unexpected error in run_agent_streaming: {str(e)}")
|
| 395 |
+
logger.error(f"Traceback: {traceback.format_exc()}")
|
| 396 |
+
|
| 397 |
+
# For unexpected errors, don't retry
|
| 398 |
+
yield "عذراً، حدث خطأ غير متوقع. يرجى المحاولة مرة أخرى أو التواصل مع الدعم الفني إذا استمرت المشكلة."
|
| 399 |
+
return
|
| 400 |
+
|
| 401 |
+
# This should never be reached, but just in case
|
| 402 |
+
logger.error(f"Maximum retries exceeded. Last error: {str(last_error)}")
|
| 403 |
+
yield "عذراً، لم أتمكن من معالجة طلبك بعد عدة محاولات. يرجى المحاولة مرة أخرى لاحقاً."
|
| 404 |
+
|
| 405 |
+
|
| 406 |
+
async def safe_run_agent_streaming(user_input: str) -> AsyncGenerator[str, None]:
|
| 407 |
+
"""
|
| 408 |
+
Streaming wrapper function with additional safety checks and input validation.
|
| 409 |
+
|
| 410 |
+
This function provides an additional layer of safety by validating input parameters,
|
| 411 |
+
checking input length constraints, and handling any critical errors that might
|
| 412 |
+
occur during streaming agent execution.
|
| 413 |
+
|
| 414 |
+
Args:
|
| 415 |
+
user_input (str): The user's input message to process
|
| 416 |
+
|
| 417 |
+
Yields:
|
| 418 |
+
str: Chunks of the agent's response as they are generated
|
| 419 |
+
|
| 420 |
+
Raises:
|
| 421 |
+
None: All exceptions are caught and handled internally
|
| 422 |
+
"""
|
| 423 |
+
try:
|
| 424 |
+
# Input type validation
|
| 425 |
+
if not isinstance(user_input, str):
|
| 426 |
+
logger.warning(f"Invalid input type received: {type(user_input)}")
|
| 427 |
+
yield "عذراً، يجب أن يكون الإدخال نصاً صالحاً."
|
| 428 |
+
return
|
| 429 |
+
|
| 430 |
+
# Input length validation
|
| 431 |
+
stripped_input = user_input.strip()
|
| 432 |
+
|
| 433 |
+
if len(stripped_input) > 1000:
|
| 434 |
+
logger.warning(f"Input too long: {len(stripped_input)} characters")
|
| 435 |
+
yield "عذراً، الرسالة طويلة جداً. يرجى اختصار سؤالك."
|
| 436 |
+
return
|
| 437 |
+
|
| 438 |
+
if len(stripped_input) == 0:
|
| 439 |
+
logger.warning("Empty input after stripping")
|
| 440 |
+
yield "عذراً، لم أتلقَ أي سؤال. يرجى إدخال سؤالك أو طلبك."
|
| 441 |
+
return
|
| 442 |
+
|
| 443 |
+
# Stream the response through the main agent function
|
| 444 |
+
async for chunk in run_agent_streaming(user_input):
|
| 445 |
+
yield chunk
|
| 446 |
+
|
| 447 |
+
except Exception as e:
|
| 448 |
+
logger.critical(f"Critical error in safe_run_agent_streaming: {str(e)}")
|
| 449 |
+
logger.critical(f"Traceback: {traceback.format_exc()}")
|
| 450 |
+
yield "عذراً، حدث خطأ خطير في النظام. يرجى التواصل مع الدعم الفني فوراً."
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
async def run_agent(user_input: str, max_retries: int = 3) -> str:
|
| 454 |
+
"""
|
| 455 |
+
Run the agent with comprehensive error handling and retry logic.
|
| 456 |
+
|
| 457 |
+
This function processes user input through the agent executor with robust
|
| 458 |
+
error handling, automatic retries for recoverable errors, and comprehensive
|
| 459 |
+
logging for debugging and monitoring.
|
| 460 |
+
|
| 461 |
+
Args:
|
| 462 |
+
user_input (str): The user's input message to process
|
| 463 |
+
max_retries (int, optional): Maximum number of retries for recoverable errors.
|
| 464 |
+
Defaults to 3.
|
| 465 |
+
|
| 466 |
+
Returns:
|
| 467 |
+
str: The agent's response or an appropriate error message in Arabic
|
| 468 |
+
|
| 469 |
+
Raises:
|
| 470 |
+
None: All exceptions are caught and handled internally
|
| 471 |
+
"""
|
| 472 |
+
# Input validation
|
| 473 |
+
if not user_input or not user_input.strip():
|
| 474 |
+
logger.warning("Empty input received")
|
| 475 |
+
return "عذراً، لم أتلقَ أي سؤال. يرجى إدخال سؤالك أو طلبك."
|
| 476 |
+
|
| 477 |
+
retry_count = 0
|
| 478 |
+
last_error = None
|
| 479 |
+
|
| 480 |
+
while retry_count <= max_retries:
|
| 481 |
+
try:
|
| 482 |
+
# Load conversation history from memory
|
| 483 |
+
chat_history = memory.load_memory_variables({})["chat_history"]
|
| 484 |
+
|
| 485 |
+
logger.info(f"Processing user input (attempt {retry_count + 1}): {user_input[:50]}...")
|
| 486 |
+
|
| 487 |
+
# Invoke the agent with input and history (synchronous call)
|
| 488 |
+
response = agent_executor.invoke({
|
| 489 |
+
"input": user_input.strip(),
|
| 490 |
+
"chat_history": chat_history
|
| 491 |
+
})
|
| 492 |
+
|
| 493 |
+
# Validate response structure
|
| 494 |
+
if not response or "output" not in response:
|
| 495 |
+
raise ValidationError("Invalid response format from agent")
|
| 496 |
+
|
| 497 |
+
if not response["output"] or not response["output"].strip():
|
| 498 |
+
raise ValidationError("Empty response from agent")
|
| 499 |
+
|
| 500 |
+
# Save conversation context to memory
|
| 501 |
+
memory.save_context(
|
| 502 |
+
{"input": user_input},
|
| 503 |
+
{"output": response["output"]}
|
| 504 |
+
)
|
| 505 |
+
|
| 506 |
+
logger.info(f"Successfully processed user input: {user_input[:50]}...")
|
| 507 |
+
return response["output"]
|
| 508 |
+
|
| 509 |
+
except RateLimitError as e:
|
| 510 |
+
retry_count += 1
|
| 511 |
+
last_error = e
|
| 512 |
+
wait_time = min(2 ** retry_count, 60) # Exponential backoff, max 60 seconds
|
| 513 |
+
|
| 514 |
+
logger.warning(
|
| 515 |
+
f"Rate limit exceeded. Retrying in {wait_time} seconds... "
|
| 516 |
+
f"(Attempt {retry_count}/{max_retries})"
|
| 517 |
+
)
|
| 518 |
+
|
| 519 |
+
if retry_count <= max_retries:
|
| 520 |
+
await asyncio.sleep(wait_time)
|
| 521 |
+
continue
|
| 522 |
+
else:
|
| 523 |
+
logger.error("Rate limit exceeded after maximum retries")
|
| 524 |
+
return "عذراً، النظام مشغول حالياً. يرجى المحاولة مرة أخرى بعد قليل."
|
| 525 |
+
|
| 526 |
+
except APIError as e:
|
| 527 |
+
retry_count += 1
|
| 528 |
+
last_error = e
|
| 529 |
+
logger.error(f"OpenAI API error: {str(e)}")
|
| 530 |
+
|
| 531 |
+
if retry_count <= max_retries:
|
| 532 |
+
await asyncio.sleep(2)
|
| 533 |
+
continue
|
| 534 |
+
else:
|
| 535 |
+
return "عذراً، حدث خطأ في الاتصال بالخدمة. يرجى المحاولة مرة أخرى لاحقاً."
|
| 536 |
+
|
| 537 |
+
except requests.exceptions.ConnectionError as e:
|
| 538 |
+
retry_count += 1
|
| 539 |
+
last_error = e
|
| 540 |
+
logger.error(f"Network connection error: {str(e)}")
|
| 541 |
+
|
| 542 |
+
if retry_count <= max_retries:
|
| 543 |
+
await asyncio.sleep(3)
|
| 544 |
+
continue
|
| 545 |
+
else:
|
| 546 |
+
return "عذراً، لا يمكنني الاتصال بالخدمة حالياً. يرجى التحقق من اتصال الإنترنت والمحاولة مرة أخرى."
|
| 547 |
+
|
| 548 |
+
except requests.exceptions.Timeout as e:
|
| 549 |
+
retry_count += 1
|
| 550 |
+
last_error = e
|
| 551 |
+
logger.error(f"Request timeout: {str(e)}")
|
| 552 |
+
|
| 553 |
+
if retry_count <= max_retries:
|
| 554 |
+
await asyncio.sleep(2)
|
| 555 |
+
continue
|
| 556 |
+
else:
|
| 557 |
+
return "عذراً، استغرق الطلب وقتاً أطول من المتوقع. يرجى المحاولة مرة أخرى."
|
| 558 |
+
|
| 559 |
+
except requests.exceptions.RequestException as e:
|
| 560 |
+
logger.error(f"Request error: {str(e)}")
|
| 561 |
+
return "عذراً، حدث خطأ في الطلب. يرجى المحاولة مرة أخرى."
|
| 562 |
+
|
| 563 |
+
except OutputParserException as e:
|
| 564 |
+
logger.error(f"Output parsing error: {str(e)}")
|
| 565 |
+
return "عذراً، حدث خطأ في معالجة الاستجابة. يرجى إعادة صياغة سؤالك والمحاولة مرة أخرى."
|
| 566 |
+
|
| 567 |
+
except ValidationError as e:
|
| 568 |
+
logger.error(f"Validation error: {str(e)}")
|
| 569 |
+
return "عذراً، حدث خطأ في التحقق من صحة البيانات. يرجى المحاولة مرة أخرى."
|
| 570 |
+
|
| 571 |
+
except ToolExecutionError as e:
|
| 572 |
+
logger.error(f"Tool execution error: {str(e)}")
|
| 573 |
+
return "عذراً، حدث خطأ أثناء تنفيذ إحدى العمليات. يرجى المحاولة مرة أخرى أو التواصل مع الدعم الفني."
|
| 574 |
+
|
| 575 |
+
except Exception as e:
|
| 576 |
+
logger.error(f"Unexpected error in run_agent: {str(e)}")
|
| 577 |
+
logger.error(f"Traceback: {traceback.format_exc()}")
|
| 578 |
+
|
| 579 |
+
# For unexpected errors, don't retry
|
| 580 |
+
return "عذراً، حدث خطأ غير متوقع. يرجى المحاولة مرة أخرى أو التواصل مع الدعم الفني إذا استمرت المشكلة."
|
| 581 |
+
|
| 582 |
+
# This should never be reached, but just in case
|
| 583 |
+
logger.error(f"Maximum retries exceeded. Last error: {str(last_error)}")
|
| 584 |
+
return "عذراً، لم أتمكن من معالجة طلبك بعد عدة محاولات. يرجى المحاولة مرة أخرى لاحقاً."
|
| 585 |
+
|
| 586 |
+
|
| 587 |
+
async def safe_run_agent(user_input: str) -> str:
|
| 588 |
+
"""
|
| 589 |
+
Wrapper function for run_agent with additional safety checks and input validation.
|
| 590 |
+
|
| 591 |
+
This function provides an additional layer of safety by validating input parameters,
|
| 592 |
+
checking input length constraints, and handling any critical errors that might
|
| 593 |
+
occur during agent execution.
|
| 594 |
+
|
| 595 |
+
Args:
|
| 596 |
+
user_input (str): The user's input message to process
|
| 597 |
+
|
| 598 |
+
Returns:
|
| 599 |
+
str: The agent's response or an appropriate error message in Arabic
|
| 600 |
+
|
| 601 |
+
Raises:
|
| 602 |
+
None: All exceptions are caught and handled internally
|
| 603 |
+
"""
|
| 604 |
+
try:
|
| 605 |
+
# Input type validation
|
| 606 |
+
if not isinstance(user_input, str):
|
| 607 |
+
logger.warning(f"Invalid input type received: {type(user_input)}")
|
| 608 |
+
return "عذراً، يجب أن يكون الإدخال نصاً صالحاً."
|
| 609 |
+
|
| 610 |
+
# Input length validation
|
| 611 |
+
stripped_input = user_input.strip()
|
| 612 |
+
|
| 613 |
+
if len(stripped_input) > 1000:
|
| 614 |
+
logger.warning(f"Input too long: {len(stripped_input)} characters")
|
| 615 |
+
return "عذراً، الرسالة طويلة جداً. يرجى اختصار سؤالك."
|
| 616 |
+
|
| 617 |
+
if len(stripped_input) == 0:
|
| 618 |
+
logger.warning("Empty input after stripping")
|
| 619 |
+
return "عذراً، لم أتلقَ أي سؤال. يرجى إدخال سؤالك أو طلبك."
|
| 620 |
+
|
| 621 |
+
# Process the input through the main agent function
|
| 622 |
+
return await run_agent(user_input)
|
| 623 |
+
|
| 624 |
+
except Exception as e:
|
| 625 |
+
logger.critical(f"Critical error in safe_run_agent: {str(e)}")
|
| 626 |
+
logger.critical(f"Traceback: {traceback.format_exc()}")
|
| 627 |
+
return "عذراً، حدث خطأ خطير في النظام. يرجى التواصل مع الدعم الفني فوراً."
|
| 628 |
+
|
| 629 |
+
|
| 630 |
+
def clear_memory() -> None:
|
| 631 |
+
"""
|
| 632 |
+
Clear the conversation memory.
|
| 633 |
+
|
| 634 |
+
This function clears all stored conversation history from memory,
|
| 635 |
+
effectively starting a fresh conversation session.
|
| 636 |
+
"""
|
| 637 |
+
try:
|
| 638 |
+
memory.clear()
|
| 639 |
+
logger.info("Conversation memory cleared successfully")
|
| 640 |
+
except Exception as e:
|
| 641 |
+
logger.error(f"Error clearing memory: {str(e)}")
|
| 642 |
+
|
| 643 |
+
|
| 644 |
+
def get_memory_summary() -> str:
|
| 645 |
+
"""
|
| 646 |
+
Get a summary of the current conversation memory.
|
| 647 |
+
|
| 648 |
+
Returns:
|
| 649 |
+
str: A summary of the conversation history stored in memory
|
| 650 |
+
"""
|
| 651 |
+
try:
|
| 652 |
+
memory_vars = memory.load_memory_variables({})
|
| 653 |
+
return str(memory_vars.get("chat_history", "No conversation history available"))
|
| 654 |
+
except Exception as e:
|
| 655 |
+
logger.error(f"Error getting memory summary: {str(e)}")
|
| 656 |
+
return "Error retrieving memory summary"
|
| 657 |
+
|
src/config.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from langchain_huggingface import HuggingFaceEmbeddings
|
| 4 |
+
from langchain_openai import ChatOpenAI
|
| 5 |
+
from huggingface_hub import login
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
import logging
|
| 8 |
+
|
| 9 |
+
# Initialize environment
|
| 10 |
+
load_dotenv()
|
| 11 |
+
|
| 12 |
+
# Setup logging
|
| 13 |
+
logging.basicConfig(level=logging.INFO)
|
| 14 |
+
logger = logging.getLogger(__name__)
|
| 15 |
+
|
| 16 |
+
# Secure HuggingFace login with error handling
|
| 17 |
+
try:
|
| 18 |
+
hf_token = os.getenv("HUGGINGFACE_HUB_TOKEN")
|
| 19 |
+
if hf_token:
|
| 20 |
+
login(hf_token)
|
| 21 |
+
logger.info("Successfully logged into HuggingFace")
|
| 22 |
+
else:
|
| 23 |
+
logger.warning("No HuggingFace token found - some features may be limited")
|
| 24 |
+
except Exception as e:
|
| 25 |
+
logger.error(f"HuggingFace login failed: {e}")
|
| 26 |
+
|
| 27 |
+
# --- File Path Configuration (Cross-platform compatible) ---
|
| 28 |
+
PROJECT_ROOT = Path(__file__).parent.parent.absolute()
|
| 29 |
+
DATA_DIR = PROJECT_ROOT / "data"
|
| 30 |
+
COMPANY_INFO_DIR = DATA_DIR / "raw_company_info"
|
| 31 |
+
PROCESSED_DIR = DATA_DIR / "processed"
|
| 32 |
+
CHUNKS_PATH = PROCESSED_DIR / "company_chunks.pkl"
|
| 33 |
+
VECTOR_STORE_DIR = PROCESSED_DIR / "vector_store"
|
| 34 |
+
|
| 35 |
+
# Ensure directories exist
|
| 36 |
+
PROCESSED_DIR.mkdir(parents=True, exist_ok=True)
|
| 37 |
+
COMPANY_INFO_DIR.mkdir(parents=True, exist_ok=True)
|
| 38 |
+
|
| 39 |
+
# --- LLM Configuration with error handling ---
|
| 40 |
+
def create_llm():
|
| 41 |
+
"""Create LLM with proper error handling and fallbacks"""
|
| 42 |
+
openai_key = os.getenv("OPENAI_API_KEY")
|
| 43 |
+
|
| 44 |
+
if not openai_key:
|
| 45 |
+
logger.error("OPENAI_API_KEY not found in environment variables")
|
| 46 |
+
raise ValueError("OpenAI API key is required. Please set OPENAI_API_KEY environment variable.")
|
| 47 |
+
|
| 48 |
+
try:
|
| 49 |
+
return ChatOpenAI(
|
| 50 |
+
model="gpt-4o-mini", # More cost-effective for demos
|
| 51 |
+
api_key=openai_key,
|
| 52 |
+
base_url=os.getenv("OPENAI_BASE_URL"), # Optional custom endpoint
|
| 53 |
+
temperature=0.1,
|
| 54 |
+
max_tokens=1024,
|
| 55 |
+
request_timeout=30, # Increased timeout for stability
|
| 56 |
+
max_retries=2,
|
| 57 |
+
streaming=True,
|
| 58 |
+
)
|
| 59 |
+
except Exception as e:
|
| 60 |
+
logger.error(f"Failed to initialize LLM: {e}")
|
| 61 |
+
raise
|
| 62 |
+
|
| 63 |
+
LLM = create_llm()
|
| 64 |
+
|
| 65 |
+
# --- Embedding Model Configuration with error handling ---
|
| 66 |
+
def create_embedding_model():
|
| 67 |
+
"""Create embedding model with proper error handling"""
|
| 68 |
+
try:
|
| 69 |
+
return HuggingFaceEmbeddings(
|
| 70 |
+
model_name="intfloat/multilingual-e5-small",
|
| 71 |
+
model_kwargs={'device': 'cpu'},
|
| 72 |
+
encode_kwargs={'normalize_embeddings': True}
|
| 73 |
+
)
|
| 74 |
+
except Exception as e:
|
| 75 |
+
logger.error(f"Failed to load embedding model: {e}")
|
| 76 |
+
# Fallback to a simpler model
|
| 77 |
+
try:
|
| 78 |
+
return HuggingFaceEmbeddings(
|
| 79 |
+
model_name="sentence-transformers/all-MiniLM-L6-v2",
|
| 80 |
+
model_kwargs={'device': 'cpu'},
|
| 81 |
+
encode_kwargs={'normalize_embeddings': True}
|
| 82 |
+
)
|
| 83 |
+
except Exception as e2:
|
| 84 |
+
logger.error(f"Fallback embedding model also failed: {e2}")
|
| 85 |
+
raise
|
| 86 |
+
|
| 87 |
+
EMBEDDING_MODEL = create_embedding_model()
|
| 88 |
+
|
| 89 |
+
# Configuration validation
|
| 90 |
+
def validate_config():
|
| 91 |
+
"""Validate all required configurations"""
|
| 92 |
+
required_env_vars = ["OPENAI_API_KEY"]
|
| 93 |
+
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
| 94 |
+
|
| 95 |
+
if missing_vars:
|
| 96 |
+
raise ValueError(f"Missing required environment variables: {missing_vars}")
|
| 97 |
+
|
| 98 |
+
# Check if data directories exist
|
| 99 |
+
if not COMPANY_INFO_DIR.exists():
|
| 100 |
+
logger.warning(f"Company info directory not found: {COMPANY_INFO_DIR}")
|
| 101 |
+
|
| 102 |
+
logger.info("Configuration validation completed")
|
| 103 |
+
|
| 104 |
+
# Run validation on import
|
| 105 |
+
try:
|
| 106 |
+
validate_config()
|
| 107 |
+
except Exception as e:
|
| 108 |
+
logger.error(f"Configuration validation failed: {e}")
|
| 109 |
+
raise e
|
src/data_loaders.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Import required libraries
|
| 2 |
+
import pandas as pd
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import List
|
| 5 |
+
from langchain.schema import Document
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
# For PDF processing - now using LangChain's PyPDFLoader
|
| 9 |
+
from langchain_community.document_loaders import PyPDFLoader
|
| 10 |
+
|
| 11 |
+
# Configure logging
|
| 12 |
+
logger = logging.getLogger(__name__)
|
| 13 |
+
|
| 14 |
+
# --- Existing functions (as provided by user) ---
|
| 15 |
+
|
| 16 |
+
# Define a placeholder for COMPANY_INFO_DIR if it's not defined in config.py
|
| 17 |
+
# In a real application, ensure config.py is accessible or pass this path.
|
| 18 |
+
try:
|
| 19 |
+
from config import COMPANY_INFO_DIR
|
| 20 |
+
except ImportError:
|
| 21 |
+
logger.warning("COMPANY_INFO_DIR not found in config.py. Using a default placeholder.")
|
| 22 |
+
COMPANY_INFO_DIR = Path("./company_info") # Placeholder path, adjust as needed
|
| 23 |
+
|
| 24 |
+
def load_faq_documents(faq_path: Path = Path(COMPANY_INFO_DIR) / "FAQ.csv") -> List[Document]:
|
| 25 |
+
"""
|
| 26 |
+
Load and process FAQ documents from CSV file.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
faq_path: Path to the FAQ CSV file
|
| 30 |
+
|
| 31 |
+
Returns:
|
| 32 |
+
List of Document objects
|
| 33 |
+
"""
|
| 34 |
+
try:
|
| 35 |
+
# Validate file exists
|
| 36 |
+
if not faq_path.exists():
|
| 37 |
+
raise FileNotFoundError(f"FAQ file not found at {faq_path}")
|
| 38 |
+
|
| 39 |
+
df = pd.read_csv(faq_path)
|
| 40 |
+
|
| 41 |
+
# Validate required columns
|
| 42 |
+
required_cols = ['Question', 'Answer']
|
| 43 |
+
if not all(col in df.columns for col in required_cols):
|
| 44 |
+
raise ValueError(f"CSV must contain columns: {required_cols}")
|
| 45 |
+
|
| 46 |
+
documents = []
|
| 47 |
+
for idx, row in df.iterrows():
|
| 48 |
+
content = f"Question: {row.get('Question', '')}\nAnswer: {row.get('Answer', '')}"
|
| 49 |
+
|
| 50 |
+
doc = Document(
|
| 51 |
+
page_content=content,
|
| 52 |
+
metadata={
|
| 53 |
+
"source": "company_faq",
|
| 54 |
+
"type": "faq",
|
| 55 |
+
"doc_id": f"{idx}",
|
| 56 |
+
"filename": faq_path.name
|
| 57 |
+
}
|
| 58 |
+
)
|
| 59 |
+
documents.append(doc)
|
| 60 |
+
|
| 61 |
+
logger.info(f"Loaded {len(documents)} FAQ documents from {faq_path.name}")
|
| 62 |
+
return documents
|
| 63 |
+
|
| 64 |
+
except Exception as e:
|
| 65 |
+
logger.error(f"Error loading FAQ documents from {faq_path.name}: {str(e)}")
|
| 66 |
+
raise
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def load_company_info(info_path: Path = Path(COMPANY_INFO_DIR) / "info.md") -> Document:
|
| 70 |
+
"""
|
| 71 |
+
Load company information from markdown file.
|
| 72 |
+
|
| 73 |
+
Args:
|
| 74 |
+
info_path: Path to the company info markdown file
|
| 75 |
+
|
| 76 |
+
Returns:
|
| 77 |
+
Document object containing company info
|
| 78 |
+
"""
|
| 79 |
+
try:
|
| 80 |
+
# Validate file exists
|
| 81 |
+
if not info_path.exists():
|
| 82 |
+
raise FileNotFoundError(f"Info file not found at {info_path}")
|
| 83 |
+
|
| 84 |
+
with open(info_path, 'r', encoding='utf-8') as f:
|
| 85 |
+
content = f.read()
|
| 86 |
+
|
| 87 |
+
doc = Document(
|
| 88 |
+
page_content=content,
|
| 89 |
+
metadata={
|
| 90 |
+
"source": "company_info",
|
| 91 |
+
"type": "general_info",
|
| 92 |
+
"filename": info_path.name,
|
| 93 |
+
"doc_id": "company_info_main"
|
| 94 |
+
}
|
| 95 |
+
)
|
| 96 |
+
logger.info(f"Loaded company info document from {info_path.name}")
|
| 97 |
+
return doc
|
| 98 |
+
|
| 99 |
+
except Exception as e:
|
| 100 |
+
logger.error(f"Error loading company info from {info_path.name}: {str(e)}")
|
| 101 |
+
raise
|
| 102 |
+
|
| 103 |
+
# --- New functions for PDF, TXT, and Image loading ---
|
| 104 |
+
|
| 105 |
+
def load_pdf_document(file_path: Path) -> List[Document]:
|
| 106 |
+
"""
|
| 107 |
+
Load text from a PDF file using LangChain's PyPDFLoader.
|
| 108 |
+
Each page is treated as a separate document.
|
| 109 |
+
|
| 110 |
+
Args:
|
| 111 |
+
file_path: Path to the PDF file.
|
| 112 |
+
|
| 113 |
+
Returns:
|
| 114 |
+
A list of Document objects, one for each page of the PDF.
|
| 115 |
+
"""
|
| 116 |
+
documents = []
|
| 117 |
+
try:
|
| 118 |
+
if not file_path.exists():
|
| 119 |
+
raise FileNotFoundError(f"PDF file not found at {file_path}")
|
| 120 |
+
|
| 121 |
+
loader = PyPDFLoader(str(file_path)) # PyPDFLoader expects a string path
|
| 122 |
+
docs = loader.load() # This returns a list of LangChain Document objects
|
| 123 |
+
|
| 124 |
+
# Enhance metadata for consistency and add source/type
|
| 125 |
+
for doc in docs:
|
| 126 |
+
doc.metadata["source"] = "uploaded_file"
|
| 127 |
+
doc.metadata["type"] = "pdf"
|
| 128 |
+
doc.metadata["filename"] = file_path.name
|
| 129 |
+
# PyPDFLoader usually adds 'page' and 'source' (which is the file path)
|
| 130 |
+
# We can use the existing 'page' if it's there or default to 0
|
| 131 |
+
page_num = doc.metadata.get("page", 0)
|
| 132 |
+
doc.metadata["doc_id"] = f"{file_path.stem}_page_{page_num + 1}" # Ensure page number is 1-indexed
|
| 133 |
+
|
| 134 |
+
documents.extend(docs)
|
| 135 |
+
|
| 136 |
+
logger.info(f"Loaded {len(documents)} pages from PDF using PyPDFLoader: {file_path.name}")
|
| 137 |
+
return documents
|
| 138 |
+
except Exception as e:
|
| 139 |
+
logger.error(f"Error loading PDF file {file_path.name} with PyPDFLoader: {str(e)}")
|
| 140 |
+
raise
|
| 141 |
+
|
| 142 |
+
def load_txt_document(file_path: Path) -> Document:
|
| 143 |
+
"""
|
| 144 |
+
Load text from a TXT file.
|
| 145 |
+
|
| 146 |
+
Args:
|
| 147 |
+
file_path: Path to the TXT file.
|
| 148 |
+
|
| 149 |
+
Returns:
|
| 150 |
+
A Document object containing the text from the file.
|
| 151 |
+
"""
|
| 152 |
+
try:
|
| 153 |
+
if not file_path.exists():
|
| 154 |
+
raise FileNotFoundError(f"TXT file not found at {file_path}")
|
| 155 |
+
|
| 156 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 157 |
+
content = f.read()
|
| 158 |
+
|
| 159 |
+
doc = Document(
|
| 160 |
+
page_content=content,
|
| 161 |
+
metadata={
|
| 162 |
+
"source": "uploaded_file",
|
| 163 |
+
"type": "txt",
|
| 164 |
+
"filename": file_path.name,
|
| 165 |
+
"doc_id": file_path.stem
|
| 166 |
+
}
|
| 167 |
+
)
|
| 168 |
+
logger.info(f"Loaded TXT file: {file_path.name}")
|
| 169 |
+
return doc
|
| 170 |
+
except Exception as e:
|
| 171 |
+
logger.error(f"Error loading TXT file {file_path.name}: {str(e)}")
|
| 172 |
+
raise
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def process_uploaded_file(file_path: Path) -> List[Document]:
|
| 176 |
+
"""
|
| 177 |
+
Determines the file extension and calls the appropriate function to process it.
|
| 178 |
+
|
| 179 |
+
Args:
|
| 180 |
+
file_path: Path to the uploaded file.
|
| 181 |
+
|
| 182 |
+
Returns:
|
| 183 |
+
A list of Document objects containing the extracted text.
|
| 184 |
+
Returns an empty list if the file type is unsupported or an error occurs.
|
| 185 |
+
"""
|
| 186 |
+
documents = []
|
| 187 |
+
try:
|
| 188 |
+
if not file_path.exists():
|
| 189 |
+
raise FileNotFoundError(f"File not found at {file_path}")
|
| 190 |
+
|
| 191 |
+
extension = file_path.suffix.lower()
|
| 192 |
+
|
| 193 |
+
if extension == '.pdf':
|
| 194 |
+
documents = load_pdf_document(file_path)
|
| 195 |
+
elif extension == '.txt':
|
| 196 |
+
documents = [load_txt_document(file_path)] # Wrap in list for consistency
|
| 197 |
+
else:
|
| 198 |
+
logger.warning(f"Unsupported file type for {file_path.name}: {extension}")
|
| 199 |
+
# Optionally, you could raise an error here if unsupported files should halt execution
|
| 200 |
+
# raise ValueError(f"Unsupported file type: {extension}")
|
| 201 |
+
return [] # Return empty list for unsupported types
|
| 202 |
+
|
| 203 |
+
except FileNotFoundError as fnfe:
|
| 204 |
+
logger.error(f"Processing failed: {fnfe}")
|
| 205 |
+
except Exception as e:
|
| 206 |
+
logger.error(f"An unexpected error occurred while processing {file_path.name}: {str(e)}")
|
| 207 |
+
|
| 208 |
+
return documents
|
src/retriever.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils import *
|
| 2 |
+
from langchain_community.retrievers import BM25Retriever
|
| 3 |
+
from langchain.retrievers import EnsembleRetriever
|
| 4 |
+
from config import logger
|
| 5 |
+
|
| 6 |
+
# Try to load existing vector store, create if not found
|
| 7 |
+
try:
|
| 8 |
+
logger.info("Loading vector store...")
|
| 9 |
+
vector_store = load_company_vector_store()
|
| 10 |
+
if vector_store:
|
| 11 |
+
logger.info("Vector store loaded successfully")
|
| 12 |
+
else:
|
| 13 |
+
# If vector_store is None, this means it didn't exist
|
| 14 |
+
logger.info("Vector store not found, creating new...")
|
| 15 |
+
company_documents = create_company_documents()
|
| 16 |
+
company_chunks = split_documents(company_documents)
|
| 17 |
+
vector_store = create_company_vector_store(company_chunks)
|
| 18 |
+
logger.info("Vector store created successfully")
|
| 19 |
+
except Exception as e:
|
| 20 |
+
# This block will handle other potential errors during the loading/creation process
|
| 21 |
+
logger.error(f"Error loading or creating vector store: {str(e)}")
|
| 22 |
+
# It might be good to exit or handle this more gracefully.
|
| 23 |
+
# For now, let's just re-raise the exception to see what's wrong.
|
| 24 |
+
raise
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# Try to load existing company chunks, create if not found
|
| 28 |
+
try:
|
| 29 |
+
logger.info("Loading company chunks...")
|
| 30 |
+
company_chunks = load_chunks()
|
| 31 |
+
if company_chunks:
|
| 32 |
+
logger.info("Company chunks loaded successfully")
|
| 33 |
+
else:
|
| 34 |
+
# If company_chunks is None, this means it didn't exist
|
| 35 |
+
logger.info("Company chunks not found, creating new...")
|
| 36 |
+
company_documents = create_company_documents()
|
| 37 |
+
company_chunks = split_documents(company_documents)
|
| 38 |
+
save_chunks(company_chunks)
|
| 39 |
+
logger.info("Company chunks created successfully")
|
| 40 |
+
|
| 41 |
+
except Exception as e:
|
| 42 |
+
# This block will handle other potential errors during the loading/creation process
|
| 43 |
+
logger.error(f"Error loading or creating company chunks: {str(e)}")
|
| 44 |
+
# It might be good to exit or handle this more gracefully.
|
| 45 |
+
# For now, let's just re-raise the exception to see what's wrong.
|
| 46 |
+
raise
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# Create vector retriever
|
| 50 |
+
logger.info("🔍 Creating vector retriever...")
|
| 51 |
+
vector_retriever = vector_store.as_retriever(search_kwargs={"k": 5})
|
| 52 |
+
|
| 53 |
+
# Create BM25 retriever
|
| 54 |
+
logger.info("📝 Creating BM25 retriever...")
|
| 55 |
+
bm25_retriever = BM25Retriever.from_documents(company_chunks)
|
| 56 |
+
bm25_retriever.k = 3
|
| 57 |
+
|
| 58 |
+
# Create hybrid retriever
|
| 59 |
+
logger.info("🔄 Creating hybrid retriever...")
|
| 60 |
+
hybrid_retriever = EnsembleRetriever(
|
| 61 |
+
retrievers=[bm25_retriever, vector_retriever],
|
| 62 |
+
weights=[0.2, 0.8]
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
logger.info("✅ Retrievers created and hybrid retriever is ready.")
|
src/text_processor.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain.text_splitter import (
|
| 2 |
+
RecursiveCharacterTextSplitter,
|
| 3 |
+
MarkdownHeaderTextSplitter
|
| 4 |
+
)
|
| 5 |
+
|
| 6 |
+
recursive_500 = RecursiveCharacterTextSplitter(
|
| 7 |
+
chunk_size=500,
|
| 8 |
+
chunk_overlap=50,
|
| 9 |
+
length_function=len,
|
| 10 |
+
separators=[
|
| 11 |
+
"\n\n", # Paragraph breaks
|
| 12 |
+
"\n", # Line breaks
|
| 13 |
+
".", # Sentences
|
| 14 |
+
",", # Clauses
|
| 15 |
+
" ", # Words
|
| 16 |
+
]
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
markdown_splitter = MarkdownHeaderTextSplitter(
|
| 21 |
+
headers_to_split_on=[
|
| 22 |
+
("#", "company_title"),
|
| 23 |
+
("##", "section"),
|
| 24 |
+
]
|
| 25 |
+
)
|
src/tools.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from langchain.tools.retriever import create_retriever_tool
|
| 2 |
+
from langchain_community.tools import TavilySearchResults
|
| 3 |
+
from langchain.tools import tool
|
| 4 |
+
from retriever import hybrid_retriever
|
| 5 |
+
import pytz
|
| 6 |
+
from datetime import datetime
|
| 7 |
+
from pydantic import BaseModel, Field
|
| 8 |
+
from typing import Optional
|
| 9 |
+
import json
|
| 10 |
+
import uuid
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
# Optimized retriever tool with faster settings
|
| 14 |
+
retriever_tool = create_retriever_tool(
|
| 15 |
+
hybrid_retriever,
|
| 16 |
+
name="company_knowledge_tool",
|
| 17 |
+
description="Fast tool for company info, history, products, and policies. Use only for Al Shifa Digital Healthcare questions."
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
# Optimized web search tool with reduced results for speed
|
| 21 |
+
websearch_tool = TavilySearchResults(
|
| 22 |
+
max_results=3,
|
| 23 |
+
include_answer=True,
|
| 24 |
+
include_raw_content=False,
|
| 25 |
+
include_images=False,
|
| 26 |
+
search_depth="basic",
|
| 27 |
+
name="Tavily_Search_Tool",
|
| 28 |
+
description="Fast search for medical questions and current info. Use for medical advice and up-to-date information."
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
@tool
|
| 33 |
+
def get_current_datetime_tool() -> str:
|
| 34 |
+
"""
|
| 35 |
+
Returns the current date, time, and day of the week for Saudi Arabia (Asia/Riyadh).
|
| 36 |
+
This is the only reliable source for date and time information. Use this tool
|
| 37 |
+
whenever a user asks about 'today', 'now', or any other time-sensitive query.
|
| 38 |
+
The output is in English but shows Saudi Arabia local time.
|
| 39 |
+
"""
|
| 40 |
+
try:
|
| 41 |
+
# Define the timezone for Saudi Arabia
|
| 42 |
+
saudi_tz = pytz.timezone('Asia/Riyadh')
|
| 43 |
+
|
| 44 |
+
# Get the current time in that timezone
|
| 45 |
+
now_saudi = datetime.now(saudi_tz)
|
| 46 |
+
|
| 47 |
+
# Manual mapping to ensure English output regardless of system locale
|
| 48 |
+
days_en = {
|
| 49 |
+
0: "Monday", 1: "Tuesday", 2: "Wednesday", 3: "Thursday",
|
| 50 |
+
4: "Friday", 5: "Saturday", 6: "Sunday"
|
| 51 |
+
}
|
| 52 |
+
months_en = {
|
| 53 |
+
1: "January", 2: "February", 3: "March", 4: "April",
|
| 54 |
+
5: "May", 6: "June", 7: "July", 8: "August",
|
| 55 |
+
9: "September", 10: "October", 11: "November", 12: "December"
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
# Get English names using manual mapping
|
| 59 |
+
day_name = days_en[now_saudi.weekday()]
|
| 60 |
+
month_name = months_en[now_saudi.month]
|
| 61 |
+
day = now_saudi.day
|
| 62 |
+
year = now_saudi.year
|
| 63 |
+
|
| 64 |
+
# Format time manually to avoid locale issues
|
| 65 |
+
hour = now_saudi.hour
|
| 66 |
+
minute = now_saudi.minute
|
| 67 |
+
|
| 68 |
+
# Convert to 12-hour format
|
| 69 |
+
if hour == 0:
|
| 70 |
+
hour_12 = 12
|
| 71 |
+
period = "AM"
|
| 72 |
+
elif hour < 12:
|
| 73 |
+
hour_12 = hour
|
| 74 |
+
period = "AM"
|
| 75 |
+
elif hour == 12:
|
| 76 |
+
hour_12 = 12
|
| 77 |
+
period = "PM"
|
| 78 |
+
else:
|
| 79 |
+
hour_12 = hour - 12
|
| 80 |
+
period = "PM"
|
| 81 |
+
|
| 82 |
+
time_str = f"{hour_12:02d}:{minute:02d} {period}"
|
| 83 |
+
|
| 84 |
+
# Create the final string
|
| 85 |
+
return f"Current date and time in Saudi Arabia: {day_name}, {month_name} {day}, {year} at {time_str}"
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
return f"Error getting current datetime: {str(e)}"
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
# --- Pydantic Model for Structured Tool Input ---
|
| 92 |
+
# All fields are now Optional to prevent validation errors at the agent level.
|
| 93 |
+
# The validation logic is moved inside the tool itself.
|
| 94 |
+
class BookingInput(BaseModel):
|
| 95 |
+
"""Inputs for the book_consultation tool."""
|
| 96 |
+
patient_name: Optional[str] = Field(None, description="The user's full name.")
|
| 97 |
+
age: Optional[str] = Field(None, description="The user's age.")
|
| 98 |
+
gender: Optional[str] = Field(None, description="The user's gender.")
|
| 99 |
+
contact_number: Optional[str] = Field(None, description="A phone number for confirmation.")
|
| 100 |
+
email: Optional[str] = Field(None, description="An email address for confirmation and reminders.")
|
| 101 |
+
reason_for_consultation: Optional[str] = Field(None, description="A brief description of the user's symptoms or reason for the visit.")
|
| 102 |
+
preferred_date: Optional[str] = Field(None, description="The user's desired date for the appointment.")
|
| 103 |
+
preferred_time: Optional[str] = Field(None, description="The user's desired time for the appointment.")
|
| 104 |
+
specialty: Optional[str] = Field(None, description="The specific medical field needed.")
|
| 105 |
+
doctor_preference: Optional[str] = Field(None, description="The name of a specific doctor, if requested.")
|
| 106 |
+
consultation_type: Optional[str] = Field(None, description="The method of consultation.")
|
| 107 |
+
|
| 108 |
+
@tool(args_schema=BookingInput)
|
| 109 |
+
def book_consultation_tool(patient_name: Optional[str] = None, age: Optional[str] = None, gender: Optional[str] = None, contact_number: Optional[str] = None, email: Optional[str] = None, reason_for_consultation: Optional[str] = None, preferred_date: Optional[str] = None, preferred_time: Optional[str] = None, consultation_type: Optional[str] = None, specialty: Optional[str] = None, doctor_preference: Optional[str] = None) -> str:
|
| 110 |
+
"""
|
| 111 |
+
Books a medical consultation. If required information is missing, it returns a message
|
| 112 |
+
asking for the missing details. Otherwise, it returns the booking data as a JSON object.
|
| 113 |
+
Email is optional and will be set to 'unknown@alshifa-care.com' if not provided.
|
| 114 |
+
"""
|
| 115 |
+
# --- 1. Internal Validation: Check for missing required information ---
|
| 116 |
+
missing_fields = []
|
| 117 |
+
if not patient_name:
|
| 118 |
+
missing_fields.append("اسم المريض الكامل (patient_name)")
|
| 119 |
+
if not age:
|
| 120 |
+
missing_fields.append("العمر (age)")
|
| 121 |
+
if not gender:
|
| 122 |
+
missing_fields.append("الجنس (gender)")
|
| 123 |
+
if not contact_number:
|
| 124 |
+
missing_fields.append("رقم التواصل (contact_number)")
|
| 125 |
+
if not reason_for_consultation:
|
| 126 |
+
missing_fields.append("سبب الاستشارة (reason_for_consultation)")
|
| 127 |
+
if not preferred_date:
|
| 128 |
+
missing_fields.append("التاريخ المفضل (preferred_date)")
|
| 129 |
+
if not preferred_time:
|
| 130 |
+
missing_fields.append("الوقت المفضل (preferred_time)")
|
| 131 |
+
# Set default email if not provided
|
| 132 |
+
if not email:
|
| 133 |
+
email = "unknown@alshifa-care.com"
|
| 134 |
+
# --- 2. Handle missing required fields ---
|
| 135 |
+
if missing_fields:
|
| 136 |
+
missing_fields_str = "\n- ".join([""] + missing_fields) # Add newline and bullet points
|
| 137 |
+
return f"""
|
| 138 |
+
عذراً، لا يمكن إتمام الحجز بسبب نقص المعلومات المطلوبة. يرجى توفير:
|
| 139 |
+
{missing_fields_str}
|
| 140 |
+
|
| 141 |
+
Sorry, we cannot complete the booking due to missing required information. Please provide:
|
| 142 |
+
{missing_fields_str}
|
| 143 |
+
"""
|
| 144 |
+
# --- 2. If all data is present, proceed with booking ---
|
| 145 |
+
booking_id = f"BK-{uuid.uuid4().hex[:8].upper()}"
|
| 146 |
+
timestamp = datetime.now().isoformat()
|
| 147 |
+
booking_data = {
|
| 148 |
+
"booking_id": booking_id,
|
| 149 |
+
"booking_timestamp": timestamp,
|
| 150 |
+
"patient_details": {"name": patient_name, "age": age, "gender": gender, "contact_number": contact_number, "email": email},
|
| 151 |
+
"consultation_details": {
|
| 152 |
+
"reason": reason_for_consultation,
|
| 153 |
+
"preferred_date": preferred_date,
|
| 154 |
+
"preferred_time": preferred_time,
|
| 155 |
+
"specialty": specialty or "Not Specified",
|
| 156 |
+
"doctor_preference": doctor_preference or "Any",
|
| 157 |
+
"type": consultation_type or "Video Call"
|
| 158 |
+
}
|
| 159 |
+
}
|
| 160 |
+
# 3. Return the final data as a formatted JSON string
|
| 161 |
+
return json.dumps(booking_data, indent=4, ensure_ascii=False)
|
src/utils.py
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pickle
|
| 2 |
+
import logging
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import List, Optional
|
| 5 |
+
from langchain.schema import Document
|
| 6 |
+
from langchain_community.vectorstores import FAISS
|
| 7 |
+
|
| 8 |
+
from config import EMBEDDING_MODEL, VECTOR_STORE_DIR, CHUNKS_PATH
|
| 9 |
+
from data_loaders import load_company_info, load_faq_documents
|
| 10 |
+
from text_processor import markdown_splitter, recursive_500
|
| 11 |
+
|
| 12 |
+
logger = logging.getLogger(__name__)
|
| 13 |
+
|
| 14 |
+
def load_company_vector_store() -> Optional[FAISS]:
|
| 15 |
+
"""Load existing vector store with proper error handling"""
|
| 16 |
+
try:
|
| 17 |
+
if Path(VECTOR_STORE_DIR).exists():
|
| 18 |
+
vector_store = FAISS.load_local(
|
| 19 |
+
str(VECTOR_STORE_DIR),
|
| 20 |
+
EMBEDDING_MODEL,
|
| 21 |
+
allow_dangerous_deserialization=True
|
| 22 |
+
)
|
| 23 |
+
logger.info("Successfully loaded existing vector store")
|
| 24 |
+
return vector_store
|
| 25 |
+
else:
|
| 26 |
+
logger.info("No existing vector store found")
|
| 27 |
+
return None
|
| 28 |
+
except Exception as e:
|
| 29 |
+
logger.error(f"Failed to load vector store: {e}")
|
| 30 |
+
return None
|
| 31 |
+
|
| 32 |
+
def create_company_vector_store(documents: List[Document]) -> Optional[FAISS]:
|
| 33 |
+
"""Create and save vector store with error handling"""
|
| 34 |
+
if not documents:
|
| 35 |
+
logger.error("No documents provided to create vector store")
|
| 36 |
+
return None
|
| 37 |
+
|
| 38 |
+
try:
|
| 39 |
+
# Ensure directory exists
|
| 40 |
+
Path(VECTOR_STORE_DIR).mkdir(parents=True, exist_ok=True)
|
| 41 |
+
|
| 42 |
+
vector_store = FAISS.from_documents(documents, EMBEDDING_MODEL)
|
| 43 |
+
vector_store.save_local(str(VECTOR_STORE_DIR))
|
| 44 |
+
logger.info(f"Successfully created and saved vector store with {len(documents)} documents")
|
| 45 |
+
return vector_store
|
| 46 |
+
except Exception as e:
|
| 47 |
+
logger.error(f"Failed to create vector store: {e}")
|
| 48 |
+
return None
|
| 49 |
+
|
| 50 |
+
def create_company_documents() -> List[Document]:
|
| 51 |
+
"""Create company documents with error handling"""
|
| 52 |
+
try:
|
| 53 |
+
company_documents = []
|
| 54 |
+
|
| 55 |
+
# Load FAQ documents
|
| 56 |
+
try:
|
| 57 |
+
faq_docs = load_faq_documents()
|
| 58 |
+
company_documents.extend(faq_docs)
|
| 59 |
+
logger.info(f"Loaded {len(faq_docs)} FAQ documents")
|
| 60 |
+
except Exception as e:
|
| 61 |
+
logger.error(f"Failed to load FAQ documents: {e}")
|
| 62 |
+
|
| 63 |
+
# Load company info
|
| 64 |
+
try:
|
| 65 |
+
company_info = load_company_info()
|
| 66 |
+
if company_info:
|
| 67 |
+
company_documents.append(company_info)
|
| 68 |
+
logger.info("Loaded company info document")
|
| 69 |
+
except Exception as e:
|
| 70 |
+
logger.error(f"Failed to load company info: {e}")
|
| 71 |
+
|
| 72 |
+
logger.info(f"Total documents loaded: {len(company_documents)}")
|
| 73 |
+
return company_documents
|
| 74 |
+
|
| 75 |
+
except Exception as e:
|
| 76 |
+
logger.error(f"Failed to create company documents: {e}")
|
| 77 |
+
return []
|
| 78 |
+
|
| 79 |
+
def split_documents(company_documents: List[Document]) -> List[Document]:
|
| 80 |
+
"""Split documents into chunks with error handling"""
|
| 81 |
+
if not company_documents:
|
| 82 |
+
logger.warning("No documents provided for splitting")
|
| 83 |
+
return []
|
| 84 |
+
|
| 85 |
+
company_chunks = []
|
| 86 |
+
|
| 87 |
+
try:
|
| 88 |
+
for i, doc in enumerate(company_documents):
|
| 89 |
+
try:
|
| 90 |
+
if doc.metadata.get("type") == "general_info":
|
| 91 |
+
# Use markdown splitter for info.md
|
| 92 |
+
split_docs = markdown_splitter.split_text(doc.page_content)
|
| 93 |
+
for d in split_docs:
|
| 94 |
+
d.metadata.update(doc.metadata)
|
| 95 |
+
company_chunks.extend(split_docs)
|
| 96 |
+
logger.debug(f"Split document {i} using markdown splitter")
|
| 97 |
+
else:
|
| 98 |
+
# Use recursive splitter for FAQs
|
| 99 |
+
split_docs = recursive_500.split_documents([doc])
|
| 100 |
+
company_chunks.extend(split_docs)
|
| 101 |
+
logger.debug(f"Split document {i} using recursive splitter")
|
| 102 |
+
|
| 103 |
+
except Exception as e:
|
| 104 |
+
logger.error(f"Failed to split document {i}: {e}")
|
| 105 |
+
continue
|
| 106 |
+
|
| 107 |
+
logger.info(f"Successfully split {len(company_documents)} documents into {len(company_chunks)} chunks")
|
| 108 |
+
return company_chunks
|
| 109 |
+
|
| 110 |
+
except Exception as e:
|
| 111 |
+
logger.error(f"Failed to split documents: {e}")
|
| 112 |
+
return []
|
| 113 |
+
|
| 114 |
+
def load_chunks() -> Optional[List[Document]]:
|
| 115 |
+
"""Load pre-processed chunks with error handling"""
|
| 116 |
+
try:
|
| 117 |
+
if Path(CHUNKS_PATH).exists():
|
| 118 |
+
with open(CHUNKS_PATH, 'rb') as f:
|
| 119 |
+
company_chunks = pickle.load(f)
|
| 120 |
+
logger.info(f"Successfully loaded {len(company_chunks)} chunks from cache")
|
| 121 |
+
return company_chunks
|
| 122 |
+
else:
|
| 123 |
+
logger.info("No cached chunks found")
|
| 124 |
+
return None
|
| 125 |
+
except Exception as e:
|
| 126 |
+
logger.error(f"Failed to load chunks: {e}")
|
| 127 |
+
return None
|
| 128 |
+
|
| 129 |
+
def save_chunks(chunks: List[Document]) -> bool:
|
| 130 |
+
"""Save processed chunks to file"""
|
| 131 |
+
try:
|
| 132 |
+
# Ensure directory exists
|
| 133 |
+
Path(CHUNKS_PATH).parent.mkdir(parents=True, exist_ok=True)
|
| 134 |
+
|
| 135 |
+
with open(CHUNKS_PATH, 'wb') as f:
|
| 136 |
+
pickle.dump(chunks, f)
|
| 137 |
+
logger.info(f"Successfully saved {len(chunks)} chunks to {CHUNKS_PATH}")
|
| 138 |
+
return True
|
| 139 |
+
except Exception as e:
|
| 140 |
+
logger.error(f"Failed to save chunks: {e}")
|
| 141 |
+
return False
|
| 142 |
+
|
| 143 |
+
def initialize_knowledge_base() -> Optional[FAISS]:
|
| 144 |
+
"""Initialize the complete knowledge base"""
|
| 145 |
+
try:
|
| 146 |
+
# Try to load existing vector store
|
| 147 |
+
vector_store = load_company_vector_store()
|
| 148 |
+
if vector_store:
|
| 149 |
+
return vector_store
|
| 150 |
+
|
| 151 |
+
# If no existing store, create new one
|
| 152 |
+
logger.info("Creating new knowledge base...")
|
| 153 |
+
|
| 154 |
+
# Load or create chunks
|
| 155 |
+
chunks = load_chunks()
|
| 156 |
+
if not chunks:
|
| 157 |
+
logger.info("No cached chunks found, processing documents...")
|
| 158 |
+
documents = create_company_documents()
|
| 159 |
+
if documents:
|
| 160 |
+
chunks = split_documents(documents)
|
| 161 |
+
if chunks:
|
| 162 |
+
save_chunks(chunks)
|
| 163 |
+
|
| 164 |
+
if chunks:
|
| 165 |
+
vector_store = create_company_vector_store(chunks)
|
| 166 |
+
return vector_store
|
| 167 |
+
else:
|
| 168 |
+
logger.error("No chunks available to create vector store")
|
| 169 |
+
return None
|
| 170 |
+
|
| 171 |
+
except Exception as e:
|
| 172 |
+
logger.error(f"Failed to initialize knowledge base: {e}")
|
| 173 |
+
return None
|
src/vector_store.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from utils import *
|
| 2 |
+
|
| 3 |
+
# Try to load existing vector store, create if not found
|
| 4 |
+
try:
|
| 5 |
+
print("Loading vector store...")
|
| 6 |
+
vector_store = load_company_vector_store()
|
| 7 |
+
if vector_store:
|
| 8 |
+
print("Vector store loaded successfully")
|
| 9 |
+
else:
|
| 10 |
+
# If vector_store is None, this means it didn't exist
|
| 11 |
+
print("Vector store not found, creating new...")
|
| 12 |
+
company_documents = create_company_documents()
|
| 13 |
+
company_chunks = split_documents(company_documents)
|
| 14 |
+
vector_store = create_company_vector_store(company_chunks)
|
| 15 |
+
print("Vector store created successfully")
|
| 16 |
+
except Exception as e:
|
| 17 |
+
# This block will handle other potential errors during the loading/creation process
|
| 18 |
+
logger.error(f"Error loading or creating vector store: {str(e)}")
|
| 19 |
+
# It might be good to exit or handle this more gracefully.
|
| 20 |
+
# For now, let's just re-raise the exception to see what's wrong.
|
| 21 |
+
raise
|
| 22 |
+
|
| 23 |
+
|