Yaser Abdelaziz commited on
Commit ·
b5acc22
1
Parent(s): c3adec4
Initial commit
Browse files- .gitignore +163 -0
- README.md +32 -13
- app.py +58 -0
- create_vector_store.py +21 -0
- data/Actual Budget Report 2022.pdf +0 -0
- data/Press Release - 2022 Results (Stock Market).pdf +0 -0
- eval/eval_results.xlsx +0 -0
- eval/eval_set.csv +8 -0
- eval/eval_set_results.csv +18 -0
- evaluate.py +48 -0
- ingest.py +20 -0
- prompts.py +45 -0
- rag.py +53 -0
- requirements.txt +3 -0
- text_splitter.py +36 -0
- utils.py +52 -0
.gitignore
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# PyInstaller
|
| 30 |
+
# Usually these files are written by a python script from a template
|
| 31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 32 |
+
*.manifest
|
| 33 |
+
*.spec
|
| 34 |
+
|
| 35 |
+
# Installer logs
|
| 36 |
+
pip-log.txt
|
| 37 |
+
pip-delete-this-directory.txt
|
| 38 |
+
|
| 39 |
+
# Unit test / coverage reports
|
| 40 |
+
htmlcov/
|
| 41 |
+
.tox/
|
| 42 |
+
.nox/
|
| 43 |
+
.coverage
|
| 44 |
+
.coverage.*
|
| 45 |
+
.cache
|
| 46 |
+
nosetests.xml
|
| 47 |
+
coverage.xml
|
| 48 |
+
*.cover
|
| 49 |
+
*.py,cover
|
| 50 |
+
.hypothesis/
|
| 51 |
+
.pytest_cache/
|
| 52 |
+
cover/
|
| 53 |
+
|
| 54 |
+
# Translations
|
| 55 |
+
*.mo
|
| 56 |
+
*.pot
|
| 57 |
+
|
| 58 |
+
# Django stuff:
|
| 59 |
+
*.log
|
| 60 |
+
local_settings.py
|
| 61 |
+
db.sqlite3
|
| 62 |
+
db.sqlite3-journal
|
| 63 |
+
|
| 64 |
+
# Flask stuff:
|
| 65 |
+
instance/
|
| 66 |
+
.webassets-cache
|
| 67 |
+
|
| 68 |
+
# Scrapy stuff:
|
| 69 |
+
.scrapy
|
| 70 |
+
|
| 71 |
+
# Sphinx documentation
|
| 72 |
+
docs/_build/
|
| 73 |
+
|
| 74 |
+
# PyBuilder
|
| 75 |
+
.pybuilder/
|
| 76 |
+
target/
|
| 77 |
+
|
| 78 |
+
# Jupyter Notebook
|
| 79 |
+
.ipynb_checkpoints
|
| 80 |
+
|
| 81 |
+
# IPython
|
| 82 |
+
profile_default/
|
| 83 |
+
ipython_config.py
|
| 84 |
+
|
| 85 |
+
# pyenv
|
| 86 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 88 |
+
# .python-version
|
| 89 |
+
|
| 90 |
+
# pipenv
|
| 91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 94 |
+
# install all needed dependencies.
|
| 95 |
+
#Pipfile.lock
|
| 96 |
+
|
| 97 |
+
# poetry
|
| 98 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 100 |
+
# commonly ignored for libraries.
|
| 101 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 102 |
+
#poetry.lock
|
| 103 |
+
|
| 104 |
+
# pdm
|
| 105 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 106 |
+
#pdm.lock
|
| 107 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
| 108 |
+
# in version control.
|
| 109 |
+
# https://pdm.fming.dev/#use-with-ide
|
| 110 |
+
.pdm.toml
|
| 111 |
+
|
| 112 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 113 |
+
__pypackages__/
|
| 114 |
+
|
| 115 |
+
# Celery stuff
|
| 116 |
+
celerybeat-schedule
|
| 117 |
+
celerybeat.pid
|
| 118 |
+
|
| 119 |
+
# SageMath parsed files
|
| 120 |
+
*.sage.py
|
| 121 |
+
|
| 122 |
+
# Environments
|
| 123 |
+
.env
|
| 124 |
+
.venv
|
| 125 |
+
env/
|
| 126 |
+
venv/
|
| 127 |
+
ENV/
|
| 128 |
+
env.bak/
|
| 129 |
+
venv.bak/
|
| 130 |
+
|
| 131 |
+
# Spyder project settings
|
| 132 |
+
.spyderproject
|
| 133 |
+
.spyproject
|
| 134 |
+
|
| 135 |
+
# Rope project settings
|
| 136 |
+
.ropeproject
|
| 137 |
+
|
| 138 |
+
# mkdocs documentation
|
| 139 |
+
/site
|
| 140 |
+
|
| 141 |
+
# mypy
|
| 142 |
+
.mypy_cache/
|
| 143 |
+
.dmypy.json
|
| 144 |
+
dmypy.json
|
| 145 |
+
|
| 146 |
+
# Pyre type checker
|
| 147 |
+
.pyre/
|
| 148 |
+
|
| 149 |
+
# pytype static type analyzer
|
| 150 |
+
.pytype/
|
| 151 |
+
|
| 152 |
+
# Cython debug symbols
|
| 153 |
+
cython_debug/
|
| 154 |
+
|
| 155 |
+
# PyCharm
|
| 156 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 157 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 158 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 159 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 160 |
+
#.idea/
|
| 161 |
+
|
| 162 |
+
# Custom
|
| 163 |
+
montaai/
|
README.md
CHANGED
|
@@ -1,13 +1,32 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
-
|
| 12 |
-
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AraRAG
|
| 2 |
+
This project is a chatbot application that can process Arabic PDF files. It uses the RAG model for generating responses in Arabic and Gradio for the user interface. The chatbot can also evaluate its own performance using the Cohere API.
|
| 3 |
+
|
| 4 |
+
## Getting Started
|
| 5 |
+
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
|
| 6 |
+
|
| 7 |
+
### Prerequisites
|
| 8 |
+
The project requires the following Python packages:
|
| 9 |
+
- gradio
|
| 10 |
+
- PyMuPDF
|
| 11 |
+
- cohere
|
| 12 |
+
|
| 13 |
+
You can install these packages using pip:
|
| 14 |
+
```bash
|
| 15 |
+
pip install -r requirements.txt
|
| 16 |
+
```
|
| 17 |
+
### Environment Variables
|
| 18 |
+
The project uses environment variables to manage sensitive information. Make sure to set these variables in your environment or in a .env file:
|
| 19 |
+
- `SPELLBOOK_BASE_URL`: The base URL for the Spellbook API.
|
| 20 |
+
- `SPELLBOOK_API_KEY`: Your Spellbook API key.
|
| 21 |
+
- `VECTOR_STORE_NAME`: The name of the vector store to be created.
|
| 22 |
+
- `OPENAI_API_KEY`: Your OpenAI API key for the RAG model.
|
| 23 |
+
- `COHERE_API_KEY`: Your Cohere API key for evaluating the chatbot's performance.
|
| 24 |
+
|
| 25 |
+
## Creating a Vector Store
|
| 26 |
+
Before running the chatbot, you need to create a vector store using the `create_vector_store.py` script. This script uses the Spellbook API to create a vector store with the name specified in the `VECTOR_STORE_NAME` environment variable.
|
| 27 |
+
|
| 28 |
+
## Running the Chatbot
|
| 29 |
+
You can run the chatbot using the `app.py` script. This script uses Gradio to create a chatbot interface. The chatbot can accept text input or a PDF file. If a PDF file is uploaded, the script will extract the text from the PDF and use it as input to the RAG model.
|
| 30 |
+
|
| 31 |
+
## Evaluating the Chatbot
|
| 32 |
+
You can evaluate the chatbot's performance using the evaluate.py script. This script uses the Cohere API to evaluate the correctness of the chatbot's answers. The script reads a CSV file with a set of questions and their correct answers, uses the chatbot to answer the questions, and then evaluates the correctness of the chatbot's answers.
|
app.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fitz
|
| 2 |
+
import gradio as gr
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv(override=True)
|
| 6 |
+
from rag import RAG
|
| 7 |
+
from utils import format_page
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def add_message(history, message):
|
| 11 |
+
if message["files"] is not None and len(message["files"]) > 0:
|
| 12 |
+
file_path = message["files"][0]
|
| 13 |
+
history.append([file_path, ""])
|
| 14 |
+
else:
|
| 15 |
+
if message["text"] is not None:
|
| 16 |
+
history.append((message["text"], None))
|
| 17 |
+
|
| 18 |
+
return history
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def bot(history, message, pdf_file_content):
|
| 22 |
+
rag = RAG()
|
| 23 |
+
if message["files"] is not None and len(message["files"]) > 0:
|
| 24 |
+
file_path = message["files"][0]
|
| 25 |
+
if file_path.endswith(".pdf"):
|
| 26 |
+
with fitz.open(file_path) as doc:
|
| 27 |
+
history[-1][1] = ""
|
| 28 |
+
for page in doc:
|
| 29 |
+
cont, page_content = format_page(page)
|
| 30 |
+
if cont:
|
| 31 |
+
continue
|
| 32 |
+
pdf_file_content += "\n\n" + page_content
|
| 33 |
+
history[-1][1] += "\n\n" + page_content
|
| 34 |
+
yield history, gr.MultimodalTextbox(value=None, interactive=False), pdf_file_content
|
| 35 |
+
rag.ingest_text(pdf_file_content)
|
| 36 |
+
else:
|
| 37 |
+
for answer in rag(message, stream=True):
|
| 38 |
+
history[-1][1] = answer
|
| 39 |
+
yield history, gr.MultimodalTextbox(value=None, interactive=False), pdf_file_content
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
with gr.Blocks() as demo:
|
| 43 |
+
pdf_file_content = gr.State("")
|
| 44 |
+
|
| 45 |
+
chatbot = gr.Chatbot(
|
| 46 |
+
[],
|
| 47 |
+
elem_id="chatbot",
|
| 48 |
+
bubble_full_width=False
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
chat_input = gr.MultimodalTextbox(interactive=True, file_types=[".pdf"], placeholder="Enter message or upload file...", show_label=False)
|
| 52 |
+
|
| 53 |
+
chat_msg = chat_input.submit(add_message, [chatbot, chat_input], chatbot)
|
| 54 |
+
bot_msg = chat_msg.then(bot, [chatbot, chat_input, pdf_file_content], [chatbot, chat_input, pdf_file_content], api_name="bot_response")
|
| 55 |
+
bot_msg.then(lambda: gr.MultimodalTextbox(interactive=True), None, [chat_input])
|
| 56 |
+
|
| 57 |
+
demo.queue()
|
| 58 |
+
demo.launch()
|
create_vector_store.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
|
| 6 |
+
load_dotenv(override=True)
|
| 7 |
+
from utils import call_spellbook_api
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def main():
|
| 11 |
+
vector_store_name = os.environ.get("VECTOR_STORE_NAME")
|
| 12 |
+
|
| 13 |
+
payload = {"name": vector_store_name}
|
| 14 |
+
|
| 15 |
+
response = call_spellbook_api(endpoint="api/v1/vector-stores", payload=payload)
|
| 16 |
+
|
| 17 |
+
print(json.dumps(response, indent=2))
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
if __name__ == "__main__":
|
| 21 |
+
main()
|
data/Actual Budget Report 2022.pdf
ADDED
|
Binary file (827 kB). View file
|
|
|
data/Press Release - 2022 Results (Stock Market).pdf
ADDED
|
Binary file (518 kB). View file
|
|
|
eval/eval_results.xlsx
ADDED
|
Binary file (8.61 kB). View file
|
|
|
eval/eval_set.csv
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
question,answer
|
| 2 |
+
ما هو تقرير وزارة المالية للأداء؟, تقوم وزارة المالية بإعداد تقارير دورية عن الأداء المالي للميزانية العامة للدولة، وتعزيز الشفافية والإفصاح عن السياسات والمبادرات التي تنفذها. وتطوير مستوى الإفصاح عن المبادرات والسياسات التي تقوم بتنفيذها. وتعد تقارير أداء ربع سنوية ونصف سنوية، بالإضافة إلى بيان الميزانية السنوي والأولي.
|
| 3 |
+
اعطيني نبذة عن مجموعة تداول ولخص لي اهم مؤشرات الاداء المالي في 2021,هو تقرير يصدر عن وزارة المالية السعودية، ويكون ذلك في نهاية العام المالي، ويضم التقرير بيانات ومؤشرات الأداء الفعلي للمالية العامة والاقتصاد خلال العام المالي المنصرم، كما يُوضح أسباب الاختلاف بين التقديرات المعتمدة في الميزانية وأدائها الفعلي. ويهدف هذا التقرير إلى دعم مبادرات الشفافية والإفصاح المالي التي تنتهجها الحكومة السعودية، بالإضافة إلى تعزيز الشفافية والإفصاح في المالية العامة، مع تقديم شرح للسياسات والمبادرات والبرامج التي تنفذها الوزارة.
|
| 4 |
+
ما هو صافي الربح الذي حققته مجموعة تداول السعودية في العام المالي 2022 بعد الزكاة؟, حققت مجموعة تداول السعودية صافي ربح قدره 424.6 مليون ريال سعودي في العام المالي 2022 بعد الزكاة.
|
| 5 |
+
ما الشركة الجديدة التي تم تأسيسها بالتعاون مع صندوق الاستثمارات العامة؟, تم تأسيس شركة سوق الكربون الطوعي الإقليمية بالتعاون مع صندوق الاستثمارات العامة.
|
| 6 |
+
ما هي الأهداف الرئيسية لإطلاق شركة سوق الكربون الطوعي الإقليمية؟, الأهداف الرئيسية لإطلاق شركة سوق الكربون الطوعي الإقليمية تشمل دعم الشركات والقطاعات في المنطقة لتمكينها من الوصول إلى الحياد الكربوني وضمان شراء أرصدة الكربون لتخفيض الانبعاثات الكربونية في سلاسل القيمة.
|
| 7 |
+
كيف تؤثر المبادرات التي أطلقتها مجموعة تداول السعودية على تحقيق أهداف رؤية المملكة 2030؟, المبادرات التي أطلقتها مجموعة تداول السعودية تساهم في تعزيز كفاءة وسيولة سوق المال السعودي، وتعزيز الأداء التشغيلي والمالي، وتوسيع قاعدة العملاء المحلية والإقليمية والعالمية، مما يدعم تحقيق أهداف رؤية المملكة 2030 لتطوير قطاع مالي متقدم يسهم في تنمية الاقتصاد الوطني.
|
| 8 |
+
ما هي النتائج الرئيسية للتعاون بين مجموعة تداول السعودية وسوق أبوظبي للأوراق المالية؟, نتج عن التعاون بين مجموعة تداول السعودية وسوق أبوظبي للأوراق المالية أول عملية إدراج مزدوج ومتزامن، مما يمثل مرحلة جديدة من التعاون بين السوق المالية السعودية والأسواق المالية الخليجية والدولية.
|
eval/eval_set_results.csv
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
question,answer,agent_answer,correctness,correctness_reason
|
| 2 |
+
ما هو تقرير وزارة المالية للأداء؟, تقوم وزارة المالية بإعداد تقارير دورية عن الأداء المالي للميزانية العامة للدولة، وتعزيز الشفافية والإفصاح عن السياسات والمبادرات التي تنفذها. وتطوير مستوى الإفصاح عن المبادرات والسياسات التي تقوم بتنفيذها. وتعد تقارير أداء ربع سنوية ونصف سنوية، بالإضافة إلى بيان الميزانية السنوي والأولي.,تقرير وزارة المالية للأداء هو وثيقة تصدرها وزارة المالية وتوفر نظرة عامة على أداء الوزارة على مدار عام معين. يغطي التقرير جوانب مختلفة مثل الإيرادات والإنفاق والرؤية والاستراتيجية والتحديات المستقبلية. يهدف التقرير إلى توفير تحديث شفاف للجمهور حول عمليات الوزارة وإنجازاتها.,True,"The agent's answer provides a valid explanation of what the Ministry of Finance Performance Report is, and matches the essence of the ground truth answer."
|
| 3 |
+
اعطيني نبذة عن مجموعة تداول ولخص لي اهم مؤشرات الاداء المالي في 2021,هو تقرير يصدر عن وزارة المالية السعودية، ويكون ذلك في نهاية العام المالي، ويضم التقرير بيانات ومؤشرات الأداء الفعلي للمالية العامة والاقتصاد خلال العام المالي المنصرم، كما يُوضح أسباب الاختلاف بين التقديرات المعتمدة في الميزانية وأدائها الفعلي. ويهدف هذا التقرير إلى دعم مبادرات الشفافية والإفصاح المالي التي تنتهجها الحكومة السعودية، بالإضافة إلى تعزيز الشفافية والإفصاح في المالية العامة، مع تقديم شرح للسياسات والمبادرات والبرامج التي تنفذها الوزارة.,"شركة تداول القابضة هي شركة مساهمة عامة مدرجة في السوق المالية السعودية (تداول). تعمل الشركة في مجال تقديم الخدمات المالية، وتمتلك عدة شركات تابعة لها تعمل في قطاعات مختلفة مرتبطة بالقطاع المالي. من أهم مؤشرات الأداء المالي للشركة لعام 2021:
|
| 4 |
+
|
| 5 |
+
1. حققت إيرادات بلغت 1,166.1 مليون ريال سعودي، بزيادة قدرها 8.1% عن العام السابق 2020.
|
| 6 |
+
|
| 7 |
+
2. بلغ صافي الدخل 587.7 مليون ريال سعودي، بزيادة قدرها 27.8% عن عام 2020.
|
| 8 |
+
|
| 9 |
+
3. حققت الشركة أرباحاً قبل الفوائد والضرائب والاستهلاك والإطفاء (EBITDA) بلغت 1,071.4 مليون ريال سعودي، بزيادة قدرها 117% عن العام الماضي.
|
| 10 |
+
|
| 11 |
+
4. بلغت أصول الشركة 7,690 مليون ريال سعودي، بينما بلغت حقوق المساهمين 4,510 مليون ريال سعودي.
|
| 12 |
+
|
| 13 |
+
هذه هي أهم مؤشرات الأداء المالي لشركة تداول القابضة خلال عام 2021 كما وردت في تقاريرهم المالية.",False,"The agent's answer doesn't match the ground truth. While the provided information is about a company named 'تداول القابضة', which is a Saudi financial services company, the ground truth mentions a report issued by the Ministry of Finance, which is different from the requested information."
|
| 14 |
+
ما هو صافي الربح الذي حققته مجموعة تداول السعودية في العام المالي 2022 بعد الزكاة؟, حققت مجموعة تداول السعودية صافي ربح قدره 424.6 مليون ريال سعودي في العام المالي 2022 بعد الزكاة.,424.6 مليون ريال سعودي,True,The agent's answer is identical to the ground truth.
|
| 15 |
+
ما الشركة الجديدة التي تم تأسيسها بالتعاون مع صندوق الاستثمارات العامة؟, تم تأسيس شركة سوق الكربون الطوعي الإقليمية بالتعاون مع صندوق الاستثمارات العامة.,تداول جروب.,False,The agent's answer 'تداول جروب' does not correspond to the ground truth answer 'شركة سوق الكربون الطوعي الإقليمية'. They don't share any key information.
|
| 16 |
+
ما هي الأهداف الرئيسية لإطلاق شركة سوق الكربون الطوعي الإقليمية؟, الأهداف الرئيسية لإطلاق شركة سوق الكربون الطوعي الإقليمية تشمل دعم الشركات والقطاعات في المنطقة لتمكينها م�� الوصول إلى الحياد الكربوني وضمان شراء أرصدة الكربون لتخفيض الانبعاثات الكربونية في سلاسل القيمة.,سوق الكربون الطوعي الإقليمية أطلقت بهدف تعزيز التنمية المستدامة ودعم الانتقال إلى اقتصاد أخضر في المنطقة. ستعمل السوق على إنشاء إطار موحد لتجارة الكربون، مما يسهل تخفيض الانبعاثات وتوفير فرص جديدة للاستثمار والشراكات في مجال الطاقة النظيفة. الهدف الرئيسي هو تشجيع وتسهيل مشاركة القطاع الخاص في مبادرات الحد من الكربون، والمساهمة في الجهود المبذولة لمواجهة تغير المناخ.,False,"The agent answer doesn't mention anything about supporting companies in the region or enabling them to access carbon neutrality. Also, the purchase of carbon credits to reduce emissions within the value chains is not included in the agent's response."
|
| 17 |
+
كيف تؤثر المبادرات التي أطلقتها مجموعة تداول السعودية على تحقيق أهداف رؤية المملكة 2030؟, المبادرات التي أطلقتها مجموعة تداول السعودية تساهم في تعزيز كفاءة وسيولة سوق المال السعودي، وتعزيز الأداء التشغيلي والمالي، وتوسيع قاعدة العملاء المحلية والإقليمية والعالمية، مما يدعم تحقيق أهداف رؤية المملكة 2030 لتطوير قطاع مالي متقدم يسهم في تنمية الاقتصاد الوطني.,تدعم مبادرات مجموعة تداول السعودية رؤية المملكة 2030 في تحفيز وتنمية سوق المال السعودي، حيث تساهم في زيادة كفاءة السوق المالية وتعزيز مكانة المملكة كمركز مالي رائد في المنطقة. كما تساعد في تطوير السوق وتوفير فرص استثمارية جديدة، مما يؤدي إلى جذب المزيد من الاستثمارات المحلية والأجنبية. بالإضافة إلى ذلك، فإن هذه المبادرات ستعمل على تحسين الأداء التشغيلي للسوق المالية السعودية وتوفير بيئة تجارية أكثر كفاءة وفعالية.,True,"The agent's answer mentions how the initiatives launched by Tadawul contribute to the vision of enhancing the efficiency of the Saudi stock market and its role as a leading financial center in the region. It also talks about the impact on operational and financial performance, which is implied in the ground truth answer."
|
| 18 |
+
ما هي النتائج الرئيسية للتعاون بين مجموعة تداول السعودية وسوق أبوظبي للأوراق المالية؟, نتج عن التعاون بين مجموعة تداول السعودية وسوق أبوظبي للأوراق المالية أول عملية إدراج مزدوج ومتزامن، مما يمثل مرحلة جديدة من التعاون بين السوق المالية السعودية والأسواق المالية الخليجية والدولية.,نجحت مجموعة تداول السعودية وسوق أبوظبي للأوراق المالية في تحقيق شراكة ناجحة تم على إثرها أول إدراج مزدوج لشركة سعودية في سوق أبوظبي للوراق المالية، وهي خطوة هامة نحو الأمام لتعزيز التعاون المالي بين البلدين. كما تم إدراج شركة أخرى في سوق أبوظبي أيضاً، مما سيساهم في إظهار فرص جديدة للاستثمار والتبادل التجاري.,False,"The agent's answer doesn't mention the simultaneous dual listing, which is the main outcome of the collaboration between the two companies, as stated in the ground truth answer."
|
evaluate.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
import cohere
|
| 5 |
+
import pandas as pd
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
load_dotenv(override=True)
|
| 9 |
+
from rag import RAG
|
| 10 |
+
from prompts import preamble, message
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def evaluate(co, question, agent_answer, ground_truth):
|
| 14 |
+
response = co.chat(
|
| 15 |
+
model='command-r',
|
| 16 |
+
message=message.format(question=question, agent_answer=agent_answer, ground_truth=ground_truth),
|
| 17 |
+
temperature=0.0,
|
| 18 |
+
chat_history=[{"role": "system", "message": preamble}],
|
| 19 |
+
prompt_truncation='AUTO',
|
| 20 |
+
connectors=[]
|
| 21 |
+
)
|
| 22 |
+
json_response = json.loads(response.text[8:-4])
|
| 23 |
+
correctness = json_response['correctness']
|
| 24 |
+
correctness_reason = json_response['correctness_reason']
|
| 25 |
+
return correctness, correctness_reason
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
if __name__ == '__main__':
|
| 29 |
+
co = cohere.Client(os.getenv('COHERE_API_KEY'))
|
| 30 |
+
rag = RAG()
|
| 31 |
+
|
| 32 |
+
def df_evaluate(row):
|
| 33 |
+
correctness, correctness_reason = evaluate(
|
| 34 |
+
co,
|
| 35 |
+
row["question"],
|
| 36 |
+
row["agent_answer"],
|
| 37 |
+
row["answer"]
|
| 38 |
+
)
|
| 39 |
+
row["correctness"] = correctness
|
| 40 |
+
row["correctness_reason"] = correctness_reason
|
| 41 |
+
return row
|
| 42 |
+
|
| 43 |
+
eval_df = pd.read_csv("eval/eval_set.csv")
|
| 44 |
+
eval_df["agent_answer"] = eval_df["question"].apply(lambda x: list(rag(x))[0])
|
| 45 |
+
eval_df = eval_df.apply(df_evaluate, axis=1)
|
| 46 |
+
eval_df.to_csv("eval/eval_set_results.csv", index=False)
|
| 47 |
+
accuracy = eval_df["correctness"].mean()
|
| 48 |
+
print(f"Accuracy: {accuracy}")
|
ingest.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
+
from utils import generate_embeddings, call_spellbook_api
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def ingest_data(vector_store_name, splits):
|
| 7 |
+
embeddings = generate_embeddings(splits)
|
| 8 |
+
document_items = [
|
| 9 |
+
{
|
| 10 |
+
"embedding": embedding,
|
| 11 |
+
"text": split
|
| 12 |
+
}
|
| 13 |
+
for split, embedding in zip(splits, embeddings)
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
payload = {"items": document_items}
|
| 17 |
+
|
| 18 |
+
response = call_spellbook_api(endpoint="api/v1/vector-stores/" + vector_store_name + "/documents", payload=payload)
|
| 19 |
+
|
| 20 |
+
print(json.dumps(response, indent=2))
|
prompts.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
prompt = """Use the following context as your learned knowledge, inside <context></context> XML tags. Notice that I extracted the following context from a pdf file.
|
| 2 |
+
|
| 3 |
+
<context>
|
| 4 |
+
{pdf_file_content}
|
| 5 |
+
</context>
|
| 6 |
+
|
| 7 |
+
When answer to user:
|
| 8 |
+
- If you don't know, just say that you don't know.
|
| 9 |
+
- If you don't know when you are not sure, ask for clarification.
|
| 10 |
+
- Avoid mentioning that you obtained the information from the context.
|
| 11 |
+
- Be concise and to the point.
|
| 12 |
+
- Avoid providing information that is not in the context.
|
| 13 |
+
- And answer according to the language of the user's question.
|
| 14 |
+
|
| 15 |
+
{message}"""
|
| 16 |
+
|
| 17 |
+
prompt_v2 = """I extracted the following Arabic text from a pdf file:
|
| 18 |
+
|
| 19 |
+
\"\"\"
|
| 20 |
+
{pdf_file_content}
|
| 21 |
+
\"\"\"
|
| 22 |
+
|
| 23 |
+
Please answer the following question in Arabic using the Arabic text above only:
|
| 24 |
+
{message}"""
|
| 25 |
+
|
| 26 |
+
eval_preamble = """Your role is to test AI agents. Your task consists in assessing whether a agent output correctly answers a question.
|
| 27 |
+
You are provided with the ground truth answer to the question. Your task is then to evaluate if the agent answer is close to the ground thruth answer.
|
| 28 |
+
|
| 29 |
+
Think step by step and consider the agent output in its entirety. Remember: you need to have a strong and sound reason to support your evaluation.
|
| 30 |
+
If the agent answer is correct, return True. If the agent answer is incorrect, return False along with the reason.
|
| 31 |
+
You must output a single JSON object with keys 'correctness' and 'correctness_reason'. Make sure you return a valid JSON object.
|
| 32 |
+
|
| 33 |
+
The question that was asked to the agent, its output, and the expected ground truth answer will be delimited with XML tags."""
|
| 34 |
+
|
| 35 |
+
eval_message = """<question>
|
| 36 |
+
{question}
|
| 37 |
+
</question>
|
| 38 |
+
|
| 39 |
+
<agent_answer>
|
| 40 |
+
{agent_answer}
|
| 41 |
+
</agent_answer>
|
| 42 |
+
|
| 43 |
+
<ground_truth>
|
| 44 |
+
{ground_truth}
|
| 45 |
+
</ground_truth>"""
|
rag.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import cohere
|
| 4 |
+
|
| 5 |
+
from prompts import prompt
|
| 6 |
+
from ingest import ingest_data
|
| 7 |
+
from text_splitter import split_text_on_tokens, tokenizer
|
| 8 |
+
from utils import generate_embeddings, call_spellbook_api
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class RAG:
|
| 12 |
+
vector_store_name = os.environ.get("VECTOR_STORE_NAME")
|
| 13 |
+
|
| 14 |
+
def ingest_text(self, text: str):
|
| 15 |
+
ingest_data(self.vector_store_name, split_text_on_tokens(text, tokenizer))
|
| 16 |
+
|
| 17 |
+
def __call__(self, message: str, stream=False):
|
| 18 |
+
payload = {"queryEmbedding": generate_embeddings([str(message)])[0], "k": 10}
|
| 19 |
+
response = call_spellbook_api(
|
| 20 |
+
endpoint="api/v1/vector-stores/" + self.vector_store_name + "/similarity-search", payload=payload
|
| 21 |
+
)
|
| 22 |
+
print(response)
|
| 23 |
+
|
| 24 |
+
docs = [item["text"] for item in response["data"]["items"]]
|
| 25 |
+
|
| 26 |
+
co = cohere.Client(os.getenv('COHERE_API_KEY'))
|
| 27 |
+
# print("Message: ", str(message))
|
| 28 |
+
# response = co.rerank(model="rerank-multilingual-v3.0", query=str(message), documents=docs, top_n=10, return_documents=True)
|
| 29 |
+
# print(response)
|
| 30 |
+
# docs = [doc.document.text for doc in response.results if doc.relevance_score > 0.1]
|
| 31 |
+
|
| 32 |
+
information = "\n\n".join(docs)
|
| 33 |
+
print(information)
|
| 34 |
+
|
| 35 |
+
answer = ""
|
| 36 |
+
print(prompt.format(pdf_file_content=information, message=message))
|
| 37 |
+
for event in co.chat_stream(
|
| 38 |
+
model='command-r',
|
| 39 |
+
message=prompt.format(pdf_file_content=information, message=message),
|
| 40 |
+
temperature=0.0,
|
| 41 |
+
chat_history=[],
|
| 42 |
+
prompt_truncation='AUTO',
|
| 43 |
+
connectors=[]
|
| 44 |
+
):
|
| 45 |
+
if event.event_type == "text-generation":
|
| 46 |
+
answer += event.text
|
| 47 |
+
|
| 48 |
+
if stream:
|
| 49 |
+
yield answer
|
| 50 |
+
elif event.event_type == "stream-end":
|
| 51 |
+
break
|
| 52 |
+
if not stream:
|
| 53 |
+
yield answer
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
PyMuPDF
|
| 3 |
+
cohere
|
text_splitter.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
from typing import Callable, List
|
| 3 |
+
|
| 4 |
+
from transformers import AutoTokenizer
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@dataclass
|
| 8 |
+
class Tokenizer:
|
| 9 |
+
chunk_overlap: int
|
| 10 |
+
tokens_per_chunk: int
|
| 11 |
+
decode: Callable[[List[int]], str]
|
| 12 |
+
encode: Callable[[str], List[int]]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def split_text_on_tokens(text: str, tokenizer: Tokenizer) -> List[str]:
|
| 16 |
+
"""Split incoming text and return chunks using tokenizer."""
|
| 17 |
+
splits: list[str] = []
|
| 18 |
+
input_ids = tokenizer.encode(text)[1:]
|
| 19 |
+
start_idx = 0
|
| 20 |
+
cur_idx = min(start_idx + tokenizer.tokens_per_chunk, len(input_ids))
|
| 21 |
+
chunk_ids = input_ids[start_idx:cur_idx]
|
| 22 |
+
while start_idx < len(input_ids):
|
| 23 |
+
splits.append(tokenizer.decode(chunk_ids))
|
| 24 |
+
start_idx += tokenizer.tokens_per_chunk - tokenizer.chunk_overlap
|
| 25 |
+
cur_idx = min(start_idx + tokenizer.tokens_per_chunk, len(input_ids))
|
| 26 |
+
chunk_ids = input_ids[start_idx:cur_idx]
|
| 27 |
+
return splits
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
tokenizer = AutoTokenizer.from_pretrained("CohereForAI/c4ai-command-r-v01")
|
| 31 |
+
tokenizer = Tokenizer(
|
| 32 |
+
chunk_overlap=50,
|
| 33 |
+
tokens_per_chunk=500,
|
| 34 |
+
decode=tokenizer.decode,
|
| 35 |
+
encode=tokenizer.encode,
|
| 36 |
+
)
|
utils.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
|
| 4 |
+
import cohere
|
| 5 |
+
# from openai import OpenAI
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def generate_embeddings(texts: str):
|
| 9 |
+
# embedding_res = OpenAI().embeddings.create(input=text, model="text-embedding-ada-002")
|
| 10 |
+
# embedding = embedding_res.data[0].embedding
|
| 11 |
+
co = cohere.Client(os.getenv('COHERE_API_KEY'))
|
| 12 |
+
response = co.embed(texts=texts, input_type='classification', embedding_types=['float'], model='embed-multilingual-v3.0')
|
| 13 |
+
embeddings = response.embeddings.float
|
| 14 |
+
return embeddings
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def call_spellbook_api(endpoint: str, payload: dict):
|
| 18 |
+
spellbook_base_url = os.environ.get("SPELLBOOK_BASE_URL")
|
| 19 |
+
spellbook_api_key = os.environ.get("SPELLBOOK_API_KEY")
|
| 20 |
+
|
| 21 |
+
headers = {
|
| 22 |
+
"accept": "application/json",
|
| 23 |
+
"content-type": "application/json",
|
| 24 |
+
"authorization": f"Bearer {spellbook_api_key}",
|
| 25 |
+
}
|
| 26 |
+
url = spellbook_base_url + endpoint if spellbook_base_url else endpoint
|
| 27 |
+
response = requests.request("POST", url, json=payload, headers=headers)
|
| 28 |
+
return response.json()
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def format_page(page):
|
| 32 |
+
# reference (modified version for Arabic): https://stackoverflow.com/questions/78200728/how-to-avoid-pymupdf-fitz-interpreting-large-gaps-between-words-as-a-newline-c
|
| 33 |
+
page_content = ""
|
| 34 |
+
words = page.get_text("words", sort=True) # words sorted vertical, then horizontal
|
| 35 |
+
if len(words) == 0:
|
| 36 |
+
return True, page_content
|
| 37 |
+
line = [words[0]] # list of words in same line
|
| 38 |
+
for w in words[1:]:
|
| 39 |
+
w0 = line[-1] # get previous word
|
| 40 |
+
if abs(w0[3] - w[3]) <= 3: # same line (approx. same bottom coord)
|
| 41 |
+
line.append(w)
|
| 42 |
+
else: # new line starts
|
| 43 |
+
line.sort(key=lambda w: w[0], reverse=True) # sort words in line right-to-left
|
| 44 |
+
# print text of line
|
| 45 |
+
text = " ".join([w[4] for w in line])
|
| 46 |
+
page_content += text + "\n"
|
| 47 |
+
line = [w] # init line list again
|
| 48 |
+
# print last line
|
| 49 |
+
text = " ".join([w[4] for w in line[::-1]])
|
| 50 |
+
page_content += text + "\n"
|
| 51 |
+
page_content += chr(12) + "\n"
|
| 52 |
+
return False, page_content
|