taha454 commited on
Commit
ebebee8
·
verified ·
1 Parent(s): 4971aa3

upload part of v1.2

Browse files
Files changed (15) hide show
  1. .dockerignore +11 -0
  2. .gitattributes +35 -35
  3. .gitignore +1 -0
  4. Consumer.py +176 -138
  5. Dockerfile +18 -18
  6. LICENSE.txt +21 -21
  7. Queue_Producer.py +26 -31
  8. README.md +12 -12
  9. Server.py +95 -75
  10. app.py +170 -116
  11. encryption_utils.py +44 -44
  12. parameters.yaml +5 -0
  13. redis_utils.py +8 -0
  14. requirements.txt +27 -18
  15. utils.py +33 -17
.dockerignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ keys.env
2
+ .git
3
+ .gitignore
4
+ __pycache__/
5
+ **/__pycache__/
6
+ *.pyc
7
+ *.pyo
8
+ *.pyd
9
+ .pytest_cache/
10
+ .mypy_cache/
11
+ .ruff_cache/
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -0,0 +1 @@
 
 
1
+ keys.env
Consumer.py CHANGED
@@ -1,138 +1,176 @@
1
- # Consumer
2
- import time
3
- import pika
4
- import os
5
- from Server import get_response
6
- import json
7
- from agent.agent_graph.StateTasks import ProblemState
8
- import argparse
9
- import redis
10
- from encryption_utils import decrypt_token_from_json
11
-
12
-
13
- ##################################################
14
- # VARIABLES
15
- ##################################################
16
- # args for this file
17
- argparse_model = argparse.ArgumentParser()
18
- argparse_model.add_argument("--id", type=int, default=0, help="Consumer ID")
19
- consumer_id = argparse_model.parse_args().id
20
-
21
-
22
- RABBITMQ_URL = os.environ["RABBITMQ_URL"]
23
- QUEUE_NAME = os.environ["QUEUE_NAME"]
24
- redis_host = os.environ["REDIS_HOST"]
25
- redis_port = os.environ["REDIS_PORT"]
26
- redis_password = os.environ["REDIS_PASSWORD"]
27
-
28
-
29
- ##################################################
30
- # PROCESSING METHODS
31
- ##################################################
32
-
33
- def redis_send(user_id,msg_id,answer):
34
- r = redis.Redis(
35
- host=redis_host,
36
- port=redis_port,
37
- decode_responses=True,
38
- username="default",
39
- password=redis_password,
40
- )
41
-
42
- success = r.set(f'ANSWER_FOR_USER_ID{user_id}_OF_{msg_id}',json.dumps(answer))
43
- return success
44
-
45
-
46
- def model_call(request, token):
47
- # تأكد إن request dict
48
- if isinstance(request, str):
49
- request = json.loads(request)
50
-
51
- # fill with last state
52
- try:
53
- state = json.loads(request.get('last_state', "")) if request.get('last_state') else {}
54
- except Exception:
55
- state = {}
56
-
57
- # fallback لو مفيش state
58
- if not state:
59
- state = {
60
- "question": request.get('prompt', ""),
61
- "memory": request.get('memory', [])
62
- }
63
-
64
- answer = get_response(
65
- request.get('prompt', ""),
66
- request.get('memory', []),
67
- token,
68
- state,
69
- request.get('user_email', ""),
70
- request.get('user_name', "")
71
- )
72
-
73
- # drop unserializable keys
74
- for k in ["llm", "rag_model"]:
75
- if k in answer:
76
- answer[k] = ""
77
-
78
- return answer
79
-
80
- def process_message(recieved_msg):
81
- # decrypt token
82
- token = decrypt_token_from_json(json.loads(recieved_msg['ht_token_encrypted_dumped']))
83
- # call the model
84
- model_answer = model_call(recieved_msg,token)
85
- # send answer to redis
86
- user_id = recieved_msg["user_id"]
87
- msg_id = recieved_msg["msg_id"]
88
- redis_send_res = redis_send(user_id,msg_id,model_answer)
89
- print({"STATUS": redis_send_res , "CONSUMER": {consumer_id}}) # add monitoring but still hide user data
90
-
91
-
92
- ##################################################
93
- # CONSUMER METHODS
94
- ##################################################
95
-
96
-
97
- def get_connection():
98
- params = pika.URLParameters(RABBITMQ_URL)
99
- return pika.BlockingConnection(params)
100
-
101
- def callback(ch, method, properties, body):
102
- ##### Recieve message and process it
103
- recieved_msg = json.loads(body.decode())
104
- print("-------------------------------------------------")
105
- print(f"MSG AT CONSUMER {consumer_id}" )
106
-
107
- ##### Process Message
108
- process_message(recieved_msg)
109
-
110
-
111
- ###### Finalize
112
- ch.basic_ack(delivery_tag=method.delivery_tag)
113
-
114
- def start_consumer():
115
- # when scalled each server has consumer
116
- params = pika.URLParameters(RABBITMQ_URL)
117
- connection = pika.BlockingConnection(params)
118
- channel = connection.channel()
119
-
120
- channel.queue_declare(queue=QUEUE_NAME, durable=True)
121
-
122
- channel.basic_qos(prefetch_count=1)
123
-
124
- channel.basic_consume(
125
- queue=QUEUE_NAME,
126
- on_message_callback=callback
127
- )
128
-
129
- print("Waiting for messages...")
130
- channel.start_consuming()
131
-
132
- ##################################################
133
- # MAIN
134
- ##################################################
135
-
136
- if __name__ == "__main__":
137
- print(f"Starting New Consumer {consumer_id}...")
138
- start_consumer()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Consumer
2
+ import time
3
+ import pika
4
+ import os
5
+ from Server import get_response,initalizeGraph
6
+ import json
7
+ import argparse
8
+ from encryption_utils import decrypt_token_from_json
9
+ from redis_utils import redis_send
10
+ import traceback
11
+ import redis
12
+ from langgraph.checkpoint.postgres import PostgresSaver
13
+ from psycopg_pool import ConnectionPool
14
+ from psycopg.rows import dict_row
15
+ import yaml
16
+ from utils import get_rabbit_connection
17
+
18
+
19
+ ##################################################
20
+ # VARIABLES and Initalization
21
+ ##################################################
22
+ # args for this file
23
+ argparse_model = argparse.ArgumentParser()
24
+ argparse_model.add_argument("--id", type=int, default=0, help="Consumer ID")
25
+ argparse_model.add_argument("--min_n_pool", type=int, default=2, help="Minimum number of connections in the pool")
26
+ argparse_model.add_argument("--max_n_pool", type=int, default=10, help="Maximum number of connections in the pool")
27
+
28
+ args = argparse_model.parse_args()
29
+ consumer_id = args.id
30
+ min_n_pool = args.min_n_pool
31
+ max_n_pool = args.max_n_pool
32
+
33
+
34
+ RABBITMQ_URL = os.environ["RABBITMQ_URL"]
35
+ QUEUE_NAME = os.environ["QUEUE_NAME"]
36
+ redis_host = os.environ["REDIS_HOST"]
37
+ redis_port = os.environ["REDIS_PORT"]
38
+ redis_password = os.environ["REDIS_PASSWORD"]
39
+
40
+
41
+
42
+ ##################################################
43
+ # Connections Initalization
44
+ ##################################################
45
+ REDIS_CONN = redis.Redis(
46
+ host=redis_host,
47
+ port=redis_port,
48
+ decode_responses=True,
49
+ username="default",
50
+ password=redis_password,
51
+ )
52
+
53
+ pool = ConnectionPool(
54
+ conninfo=os.environ["NEON_POSTGRES_URL"],
55
+ min_size=min_n_pool,
56
+ max_size=max_n_pool,
57
+ kwargs={
58
+ "autocommit": True,
59
+ "row_factory": dict_row,
60
+ },
61
+ )
62
+
63
+ CHECKPOINTER = PostgresSaver(pool)
64
+ _, GRAPH = initalizeGraph(CHECKPOINTER)
65
+
66
+ ##################################################
67
+ # PROCESSING METHODS
68
+ ##################################################
69
+
70
+
71
+ def model_call(request, token):
72
+ # تأكد إن request dict
73
+ if isinstance(request, str):
74
+ request = json.loads(request)
75
+
76
+
77
+
78
+ answer = get_response(
79
+ GRAPH,
80
+ request.get('prompt', request.get('msg', "")),
81
+ token,
82
+ request.get('user_email', ""),
83
+ request.get('user_id', ""),
84
+ request.get('chat_id', ""),
85
+
86
+ )
87
+
88
+ return answer
89
+
90
+ def process_message(recieved_msg):
91
+ # decrypt token
92
+ token = decrypt_token_from_json(json.loads(recieved_msg['ht_token_encrypted_dumped']))
93
+ # call the model
94
+ model_answer = model_call(recieved_msg,token)
95
+ # send answer to redis
96
+ user_id = recieved_msg["user_id"]
97
+ chat_id = recieved_msg["chat_id"]
98
+ redis_send_res = redis_send(REDIS_CONN,user_id,chat_id,model_answer)
99
+ print({"STATUS": redis_send_res , "CONSUMER": {consumer_id}}) # add monitoring but still hide user data
100
+
101
+
102
+ ##################################################
103
+ # CONSUMER METHODS
104
+ ##################################################
105
+
106
+
107
+
108
+ def callback(ch, method, properties, body):
109
+ recieved_msg = None
110
+ try:
111
+ ##### Recieve message and process it
112
+ recieved_msg = json.loads(body.decode())
113
+ print("-------------------------------------------------")
114
+ print(f"MSG AT CONSUMER {consumer_id}" )
115
+
116
+ ##### Process Message
117
+ process_message(recieved_msg)
118
+
119
+ except Exception as e:
120
+ print({"STATUS": "error", "CONSUMER": consumer_id, "ERROR": type(e).__name__})
121
+ traceback.print_exc()
122
+ if isinstance(recieved_msg, dict) and "user_id" in recieved_msg and "chat_id" in recieved_msg:
123
+ try:
124
+ redis_send(REDIS_CONN,recieved_msg["user_id"], recieved_msg["chat_id"], {"status": "error", "message": "processing_failed"})
125
+ except Exception as redis_error:
126
+ print({"STATUS": "redis_error", "CONSUMER": consumer_id, "ERROR": type(redis_error).__name__})
127
+
128
+ finally:
129
+ ###### Finalize
130
+ ch.basic_ack(delivery_tag=method.delivery_tag)
131
+
132
+ def start_consumer():
133
+ connection = get_rabbit_connection()
134
+ channel = connection.channel()
135
+
136
+ channel.queue_declare(
137
+ queue=QUEUE_NAME,
138
+ durable=True
139
+ )
140
+
141
+ channel.basic_qos(
142
+ prefetch_count=1
143
+ )
144
+
145
+ channel.basic_consume(
146
+ queue=QUEUE_NAME,
147
+ on_message_callback=callback
148
+ )
149
+
150
+ print("Waiting for RabbitMQ messages...")
151
+
152
+ channel.start_consuming()
153
+
154
+
155
+ ##################################################
156
+ # MAIN
157
+ ##################################################
158
+
159
+ if __name__ == "__main__":
160
+
161
+ print(f"Starting New Consumer {consumer_id}...")
162
+ try:
163
+ start_consumer()
164
+ print(f"Consumer {consumer_id} Done.")
165
+ except(Exception, KeyboardInterrupt) as e:
166
+ print(f"Consumer {consumer_id} stopped. Reason: {type(e).__name__}")
167
+ finally:
168
+ print(f"Consumer {consumer_id} is closing connections...")
169
+ # close all connections
170
+ pool.close()
171
+ REDIS_CONN.close()
172
+
173
+
174
+
175
+
176
+
Dockerfile CHANGED
@@ -1,18 +1,18 @@
1
- FROM python:3.11-slim
2
-
3
- ENV PYTHONDONTWRITEBYTECODE=1
4
- ENV PYTHONUNBUFFERED=1
5
-
6
- WORKDIR /app
7
-
8
- COPY requirements.txt ./
9
-
10
- RUN pip install --no-cache-dir --upgrade pip && \
11
- pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu && \
12
- pip install --no-cache-dir -r requirements.txt
13
-
14
- COPY . .
15
-
16
-
17
-
18
- CMD ["python", "-u", "app.py"]
 
1
+ FROM python:3.11-slim
2
+
3
+ ENV PYTHONDONTWRITEBYTECODE=1
4
+ ENV PYTHONUNBUFFERED=1
5
+
6
+ WORKDIR /app
7
+
8
+ COPY requirements.txt ./
9
+
10
+ RUN pip install --no-cache-dir --upgrade pip && \
11
+ pip install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cpu && \
12
+ pip install --no-cache-dir -r requirements.txt
13
+
14
+ COPY . .
15
+
16
+
17
+
18
+ CMD ["python", "-u", "app.py"]
LICENSE.txt CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 TahaFawzyElshrif
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.
 
1
+ MIT License
2
+
3
+ Copyright (c) 2025 TahaFawzyElshrif
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.
Queue_Producer.py CHANGED
@@ -1,31 +1,26 @@
1
- # Producer
2
- import pika
3
- import os
4
- RABBITMQ_URL = os.environ["RABBITMQ_URL"]
5
- QUEUE_NAME = os.environ["QUEUE_NAME"]
6
-
7
- def get_connection():
8
- params = pika.URLParameters(RABBITMQ_URL)
9
- return pika.BlockingConnection(params)
10
-
11
- connection = get_connection()
12
- def send_message(data: dict):
13
- connection = get_connection()
14
- channel = connection.channel()
15
-
16
- channel.queue_declare(queue=QUEUE_NAME, durable=True)
17
-
18
- message = str(data)
19
-
20
- channel.basic_publish(
21
- exchange='',
22
- routing_key=QUEUE_NAME,
23
- body=message,
24
- properties=pika.BasicProperties(
25
- delivery_mode=2, # make message persistent
26
- )
27
- )
28
-
29
- connection.close()
30
-
31
- return {"status": "sent", "data": data}
 
1
+ # Producer
2
+ import pika
3
+ import os
4
+ QUEUE_NAME = os.environ["QUEUE_NAME"]
5
+
6
+
7
+ def send_message(RABBITMQ_CONNECTION,data: dict):
8
+ connection = RABBITMQ_CONNECTION
9
+ channel = connection.channel()
10
+
11
+ channel.queue_declare(queue=QUEUE_NAME, durable=True)
12
+
13
+ message = str(data)
14
+
15
+ channel.basic_publish(
16
+ exchange='',
17
+ routing_key=QUEUE_NAME,
18
+ body=message,
19
+ properties=pika.BasicProperties(
20
+ delivery_mode=2, # make message persistent
21
+ )
22
+ )
23
+
24
+
25
+
26
+ return {"status": "sent", "data": data}
 
 
 
 
 
README.md CHANGED
@@ -1,12 +1,12 @@
1
- ---
2
- title: CodeBuddyAI
3
- emoji: 📚
4
- colorFrom: green
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- short_description: the agent model of CodeBuddy Project
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: CodeBuddyAI
3
+ emoji: 📚
4
+ colorFrom: green
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ short_description: the agent model of CodeBuddy Project
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
Server.py CHANGED
@@ -1,75 +1,95 @@
1
- from langchain_huggingface import HuggingFaceEndpoint,ChatHuggingFace
2
- from langchain_core.messages import HumanMessage,SystemMessage
3
- import os
4
- import pandas as pd
5
- from agent.agent_graph.graph import compiled_graph
6
- from agent.rag.rag import rag_text_chooser
7
- import sys
8
- import os
9
- from agent.agent_graph.StateTasks import Available_Tasks
10
- from agent.tools.PDF import PDF_generator_Node
11
- from agent.tools.email import EMAIL_sender_Node
12
- from agent.agent_graph.Graph_Nodes import get_llm_answer
13
- from agent.llm.prompts import NODES_Prompts
14
- import dotenv
15
-
16
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
17
- dotenv.load_dotenv("/content/drive/MyDrive/study/Projects/keys.env")
18
- def get_response(prompt,memory,hf_key,state,user_email,user_name):
19
- # Setting up models
20
- os.environ["HF_TOKEN"] = hf_key
21
- llm_gpt = HuggingFaceEndpoint(
22
- repo_id="openai/gpt-oss-20b",#"deepseek-ai/DeepSeek-V3.2-Exp",#"openai/gpt-oss-20b",
23
- task='conversational',
24
- provider="auto",
25
- max_new_tokens=2048
26
- )
27
- llm_gpt = ChatHuggingFace(llm=llm_gpt)
28
-
29
- print("RAG_PATH ",os.path.join(os.path.dirname(__file__), 'agent' ,'rag', 'rag.xlsx'), os.path.exists(os.path.join(os.path.dirname(__file__), 'agent' ,'rag', 'rag.xlsx')))
30
-
31
- rag_model = rag_text_chooser(os.path.join(os.path.dirname(__file__), 'agent' ,'rag', 'rag.xlsx'))
32
-
33
- # update state
34
- state["question"] = prompt
35
- state["memory"] = memory
36
- state["llm"] = llm_gpt
37
- state["rag_model"] = rag_model
38
-
39
- call = compiled_graph.invoke(state)
40
-
41
- save_send_email(call,user_email,user_name)
42
- os.environ["HF_TOKEN"] = "" # to prevent keep it in env for other calls and for security
43
-
44
- return call
45
-
46
- def save_send_email(call,user_email,user_name):
47
- if ("all_ok" in call.keys()):
48
- if (call['all_ok']== True):
49
- if (call['question_type'] in [Available_Tasks.LAPTOP_CHOOSE.value ,
50
- Available_Tasks.QUESTION.value ,
51
- Available_Tasks.ROADMAP.value]):
52
- email_txt = get_llm_answer(model_llm=call['llm'],messages=[HumanMessage(content=("اسم الزميل لتستخدمه هو : "+ user_name +"/n/n")+ NODES_Prompts.Email_text.value + call['question'] + str(call['memory']) + call['question_type'] + call['answer'])])
53
- title = get_llm_answer(model_llm=call['llm'],messages=[HumanMessage(content=NODES_Prompts.Email_title.value + call['question'] + str(call['memory']) + call['question_type']+ call['answer'])])
54
-
55
- import tempfile
56
- path_pdf = ''
57
- with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_file:
58
- path_pdf = tmp_file.name
59
- # هنا تكتب الكود اللي بيولد الملف
60
- print("PDF path:", path_pdf)
61
-
62
- # بعد ما تخلص من الملف ممكن تحذفه
63
- # import os
64
- # os.remove(path_pdf)
65
-
66
-
67
- #path_pdf ="/content/drive/MyDrive/study/Projects/CodeBuddyAI/tmp.pdf"
68
- PDF_generator_Node(call['answer'],title,path_pdf)
69
- #EMAIL_sender_Node(user_email,email_txt,title,path_pdf)
70
- import os
71
- os.remove(path_pdf)
72
- print("Done")
73
-
74
-
75
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from langchain_huggingface import HuggingFaceEndpoint,ChatHuggingFace
2
+ from langchain_core.messages import HumanMessage,SystemMessage
3
+ import os
4
+ import pandas as pd
5
+ import sys
6
+ import os
7
+ from agent.agent_graph.StateTasks import Available_Tasks
8
+ import dotenv
9
+ from agent.agent_graph.graph import AgentGraph
10
+ from langgraph.types import Command
11
+ from langchain_core.messages import HumanMessage
12
+ from utils import path_rag_file
13
+
14
+
15
+
16
+ def initalizeGraph(checkpointer,hf_key=None):
17
+ if hf_key:
18
+ os.environ["HF_TOKEN"] = hf_key
19
+ graph_obj = AgentGraph(checkpointer,path_rag_file)
20
+ graph = graph_obj.get_graph()
21
+
22
+ return graph_obj,graph
23
+
24
+
25
+
26
+
27
+ def get_response(graph, prompt,hf_key,user_email,user_id,chat_id):
28
+ # Setting up models
29
+ os.environ["HF_TOKEN"] = hf_key
30
+
31
+ try:
32
+ config = {"configurable": {"thread_id": user_id+"_"+chat_id}}
33
+
34
+ state = graph.get_state(config)
35
+
36
+ waiting_resume = len(state.tasks) > 0 # if there are old conversations, we need to resume the conversation instead of starting a new one , so meaning is do we need to resume the conversation or not
37
+
38
+ call = _chat_api(graph,prompt,config,waiting_resume )
39
+
40
+ #save_send_email(call,user_email,user_name)
41
+ return call
42
+
43
+ finally:
44
+ os.environ["HF_TOKEN"] = "" # to prevent keep it in env for other calls and for security
45
+
46
+ def _chat_api(
47
+ graph,
48
+ user_text: str,
49
+ config: dict,
50
+ waiting_resume: bool = False,
51
+ ) -> dict:
52
+ """
53
+ One API request -> One response.
54
+ """
55
+
56
+ if waiting_resume:
57
+ # resume the conversation
58
+ result = graph.invoke(
59
+ Command(resume=user_text),
60
+ config=config,
61
+ )
62
+ else:
63
+ # new conversation
64
+ result = graph.invoke(
65
+ {"messages": [HumanMessage(content=user_text)]},
66
+ config=config,
67
+ )
68
+
69
+ if "__interrupt__" in result:
70
+ interrupt = result["__interrupt__"][0]
71
+
72
+ return {
73
+ "status": "interrupt",
74
+ "question": interrupt.value["question"],
75
+ }
76
+
77
+ if "final_ans_report" in result and len(result["final_ans_report"]) > 400: #big enough to report not just the intermediate answer
78
+ return {
79
+ "status": "finished",
80
+ "answer": result["messages"][-1].content,
81
+ "report": result["final_ans_report"],
82
+ }
83
+
84
+ return {
85
+ "status": "finished",
86
+ "answer": result["messages"][-1].content,
87
+ }
88
+
89
+
90
+ def save_send_email(call,user_email,user_name):
91
+ pass
92
+
93
+
94
+
95
+
app.py CHANGED
@@ -1,116 +1,170 @@
1
- from urllib import request
2
-
3
- from fastapi import FastAPI
4
- from fastapi.responses import JSONResponse
5
- import sentry_sdk
6
- import uvicorn
7
- import sys
8
- import os
9
- import json
10
- sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))
11
- from agent.agent_graph.StateTasks import ProblemState
12
- import subprocess
13
- from Queue_Producer import send_message
14
- import redis
15
- from utils import RequestModel, RequestAnswer
16
- from Consumer import redis_send
17
- import psutil
18
-
19
-
20
- ##################################################
21
- # VARIABLES
22
- ##################################################
23
- redis_host = os.environ["REDIS_HOST"]
24
- redis_port = os.environ["REDIS_PORT"]
25
- redis_password = os.environ["REDIS_PASSWORD"]
26
-
27
-
28
- ##################################################
29
- # START CONSUMERS in a separate process
30
- ##################################################
31
- for i in range(1,4): # Start 3 consumers
32
- subprocess.Popen(['python','-u','Consumer.py', '--id', str(i)])
33
-
34
-
35
- ##################################################
36
- # START API and METHODS
37
- ##################################################
38
-
39
- # Create Redis connection (global to make the get very light)
40
- redis_conn = redis.Redis(
41
- host=redis_host,
42
- port=redis_port,
43
- decode_responses=True,
44
- username="default",
45
- password=redis_password,
46
- )
47
-
48
- # model and rag are not global for better security ,at least for this version
49
-
50
- # Create app instance
51
- app = FastAPI()
52
-
53
- # Create Sentry Monitoring for better error tracking and performance monitoring
54
- sentry_sdk.init(
55
- dsn=os.environ["SENTRY_DSN"],
56
- # see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
57
- send_default_pii=True,
58
- )
59
-
60
- app = FastAPI()
61
- #redis_send(request.user_id,request.msg_id,{"status": "pending"}) # for performance test
62
-
63
- print("Starting API Server...")
64
-
65
-
66
- ##################################################
67
- # ROUTES
68
- ##################################################
69
-
70
- @app.get("/")
71
- def read_root():
72
- return {"message": "Hello From CodeBuddyAI!"}
73
-
74
-
75
- @app.get("/metrics")
76
- def metrics():
77
- return {
78
- "cpu": psutil.cpu_percent(),
79
- "ram": psutil.virtual_memory().percent
80
- }
81
-
82
-
83
- @app.api_route("/health", methods=["GET", "HEAD", "POST", "OPTIONS"])
84
- def get_health():
85
- return JSONResponse({"status": "ok"})
86
-
87
-
88
- @app.post("/Message/Send/")
89
- def call(request: RequestModel):
90
- redis_send(request.user_id,request.msg_id,{"status": "pending"})
91
- return send_message(json.dumps(request.model_dump()))
92
-
93
-
94
- @app.post("/Message/Answer/")
95
- def call(request: RequestAnswer):
96
- ## MUST BE LIGHTWEIGHT, JUST CHECK IF ANSWER IS READY IN REDIS, IF YES RETURN IT, ELSE RETURN PENDING
97
- try:
98
- answer = redis_conn.get(f'ANSWER_FOR_USER_ID{request.user_id}_OF_{request.msg_id}')
99
- if answer is None:
100
- return {"status": "error"}
101
- elif "status" in answer and json.loads(answer)["status"] == "pending":
102
- return {"status": "pending"}
103
- else:
104
-
105
- redis_conn.delete(f'ANSWER_FOR_USER_ID{request.user_id}_OF_{request.msg_id}') # Clean up after fetching for memory and better secure as double call is wrong
106
- return {"status": "ready", "data": json.loads(answer)}
107
-
108
- except Exception as e:
109
- print(f"Error fetching answer from Redis: {e}")
110
- return {"status": "error", "message": str(e)}
111
-
112
-
113
-
114
-
115
- if __name__ == "__main__":
116
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from contextlib import asynccontextmanager
2
+ from urllib import request
3
+
4
+ from fastapi import FastAPI
5
+ from fastapi.responses import JSONResponse
6
+ import sentry_sdk
7
+ import uvicorn
8
+ import sys
9
+ import os
10
+ import json
11
+ import dotenv
12
+ from langgraph.checkpoint.postgres import PostgresSaver
13
+ from psycopg_pool import ConnectionPool
14
+ from psycopg.rows import dict_row
15
+ import pika
16
+ import yaml
17
+ from utils import get_rabbit_connection
18
+
19
+ parameters_obj = None
20
+
21
+ ##################################################
22
+ # VARIABLES
23
+ ##################################################
24
+ from utils import * # by default utils in same directory as app.py, so no need to add it to sys.path
25
+
26
+ sys.path.append(os.path.abspath(current_dir))
27
+ #sys.path.append(os.path.abspath(os.path.join(current_dir, '..', '..'))) # directory of the app.py with 2 upper folders
28
+ dotenv.load_dotenv(path_env) # won't affet when the env variables are already set in the system, but will set them if they are not set yet
29
+
30
+
31
+ redis_host = os.environ["REDIS_HOST"]
32
+ redis_port = os.environ["REDIS_PORT"]
33
+ redis_password = os.environ["REDIS_PASSWORD"]
34
+
35
+
36
+
37
+
38
+ import subprocess
39
+ from Queue_Producer import send_message
40
+ import redis
41
+ from redis_utils import redis_send
42
+ import psutil
43
+
44
+
45
+
46
+
47
+ ##################################################
48
+ # START API and METHODS
49
+ ##################################################
50
+
51
+ # Create Redis connection (global to make the get very light)
52
+ REDIS_CONN = None
53
+ # Create app instance
54
+ CONSUMER_PROCESSES = []
55
+ RABBITMQ_CONNECTION = None
56
+
57
+
58
+ RABBITMQ_URL = os.environ["RABBITMQ_URL"]
59
+
60
+
61
+
62
+ @asynccontextmanager
63
+ async def lifespan(app: FastAPI):
64
+ global REDIS_CONN, CHECKPOINTER, RABBITMQ_CONNECTION,parameters_obj
65
+
66
+ with open(yaml_path, 'r') as file:
67
+ parameters_obj = yaml.safe_load(file)
68
+
69
+ min_n_pool = parameters_obj.get("min_n_pool", 2)
70
+ max_n_pool = parameters_obj.get("max_n_pool", 10)
71
+ n_consumers = parameters_obj.get("n_consumers", 3)
72
+
73
+ for i in range(1,1+n_consumers): # Start 3 consumers
74
+ print(f"Starting consumer {i}")
75
+ CONSUMER_PROCESSES.append(subprocess.Popen(['python','-u',consumer_path, '--id', str(i), '--min_n_pool', str(min_n_pool), '--max_n_pool', str(max_n_pool)])) # Start consumer.py with id and pool size as arguments
76
+
77
+
78
+
79
+ RABBITMQ_CONNECTION = get_rabbit_connection()
80
+
81
+
82
+ # Init Redis
83
+ REDIS_CONN = redis.Redis(
84
+ host=redis_host,
85
+ port=redis_port,
86
+ decode_responses=True,
87
+ username="default",
88
+ password=redis_password,
89
+ )
90
+
91
+
92
+ # Create Sentry Monitoring for better error tracking and performance monitoring
93
+ sentry_sdk.init(
94
+ dsn=os.environ["SENTRY_DSN"],
95
+ # see https://docs.sentry.io/platforms/python/data-management/data-collected/ for more info
96
+ send_default_pii=False, # more secure, don't send user info, but can be set to True if needed
97
+ )
98
+
99
+
100
+ yield
101
+
102
+ # Terminate processes and connections on shutdown
103
+ for process in CONSUMER_PROCESSES:
104
+ process.terminate()
105
+
106
+ RABBITMQ_CONNECTION.close()
107
+
108
+ app = FastAPI(lifespan=lifespan)
109
+
110
+
111
+
112
+ #redis_send(REDIS_CONN,request.user_id,request.chat_id,{"status": "pending"}) # for performance test
113
+
114
+ print("Starting API Server...")
115
+
116
+
117
+ ##################################################
118
+ # ROUTES
119
+ ##################################################
120
+
121
+ @app.get("/")
122
+ def read_root():
123
+ return {"message": "Hello From CodeBuddyAI!"}
124
+
125
+
126
+ @app.get("/metrics")
127
+ def metrics():
128
+ return {
129
+ "cpu": psutil.cpu_percent(),
130
+ "ram": psutil.virtual_memory().percent,
131
+ "consumers": [{"pid": p.pid, "status": p.poll()} for p in CONSUMER_PROCESSES],
132
+
133
+ }
134
+
135
+
136
+ @app.api_route("/health", methods=["GET", "HEAD", "POST", "OPTIONS"])
137
+ def get_health():
138
+ return JSONResponse({"status": "ok"})
139
+
140
+
141
+ @app.post("/Message/Send/")
142
+ def call(request: RequestModel):
143
+ redis_send(REDIS_CONN,request.user_id,request.chat_id,{"status": "pending"})
144
+ return send_message(RABBITMQ_CONNECTION, json.dumps(request.model_dump()))
145
+
146
+
147
+ @app.post("/Message/Answer/")
148
+ def call(request: RequestAnswer):
149
+ ## MUST BE LIGHTWEIGHT, JUST CHECK IF ANSWER IS READY IN REDIS, IF YES RETURN IT, ELSE RETURN PENDING
150
+ try:
151
+ answer = REDIS_CONN.get(f'ANSWER_FOR_USER_ID{request.user_id}_OF_{request.chat_id}')
152
+ if answer is None:
153
+ return {"status": "error"}
154
+ elif "status" in answer and json.loads(answer)["status"] == "pending":
155
+ return {"status": "pending"}
156
+ else:
157
+
158
+ REDIS_CONN.delete(f'ANSWER_FOR_USER_ID{request.user_id}_OF_{request.chat_id}') # Clean up after fetching for memory and better secure as double call is wrong
159
+ return {"status": "ready", "data": json.loads(answer)}
160
+
161
+ except Exception as e:
162
+ print(f"Error fetching answer from Redis: {e}")
163
+ return {"status": "error", "message": str(e)}
164
+
165
+
166
+
167
+
168
+ if __name__ == "__main__":
169
+ uvicorn.run(app, host="0.0.0.0", port=7860)
170
+
encryption_utils.py CHANGED
@@ -1,44 +1,44 @@
1
- import os
2
- import hashlib
3
- from cryptography.hazmat.primitives.ciphers.aead import AESGCM
4
-
5
-
6
- # ===== Key (same idea as Node) =====
7
- def get_key():
8
- raw_key = os.environ["TOKEN_KEY_ENCRYPTION"].encode()
9
- return hashlib.sha256(raw_key).digest() # 32 bytes
10
-
11
-
12
- # ===== Encrypt =====
13
- def encrypt_token_to_json(token: str) -> dict:
14
- key = get_key()
15
- aesgcm = AESGCM(key)
16
-
17
- iv = os.urandom(12) # same as crypto.randomBytes(12)
18
-
19
- encrypted = aesgcm.encrypt(iv, token.encode(), None)
20
-
21
- ciphertext = encrypted[:-16]
22
- tag = encrypted[-16:]
23
-
24
- return {
25
- "iv": iv.hex(),
26
- "data": ciphertext.hex(),
27
- "tag": tag.hex(),
28
- }
29
-
30
-
31
- # ===== Decrypt =====
32
- def decrypt_token_from_json(enc: dict) -> str:
33
- key = get_key()
34
- aesgcm = AESGCM(key)
35
-
36
- iv = bytes.fromhex(enc["iv"])
37
- ciphertext = bytes.fromhex(enc["data"])
38
- tag = bytes.fromhex(enc["tag"])
39
-
40
- encrypted = ciphertext + tag
41
-
42
- decrypted = aesgcm.decrypt(iv, encrypted, None)
43
-
44
- return decrypted.decode()
 
1
+ import os
2
+ import hashlib
3
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
4
+
5
+
6
+ # ===== Key (same idea as Node) =====
7
+ def get_key():
8
+ raw_key = os.environ["TOKEN_KEY_ENCRYPTION"].encode()
9
+ return hashlib.sha256(raw_key).digest() # 32 bytes
10
+
11
+
12
+ # ===== Encrypt =====
13
+ def encrypt_token_to_json(token: str) -> dict:
14
+ key = get_key()
15
+ aesgcm = AESGCM(key)
16
+
17
+ iv = os.urandom(12) # same as crypto.randomBytes(12)
18
+
19
+ encrypted = aesgcm.encrypt(iv, token.encode(), None)
20
+
21
+ ciphertext = encrypted[:-16]
22
+ tag = encrypted[-16:]
23
+
24
+ return {
25
+ "iv": iv.hex(),
26
+ "data": ciphertext.hex(),
27
+ "tag": tag.hex(),
28
+ }
29
+
30
+
31
+ # ===== Decrypt =====
32
+ def decrypt_token_from_json(enc: dict) -> str:
33
+ key = get_key()
34
+ aesgcm = AESGCM(key)
35
+
36
+ iv = bytes.fromhex(enc["iv"])
37
+ ciphertext = bytes.fromhex(enc["data"])
38
+ tag = bytes.fromhex(enc["tag"])
39
+
40
+ encrypted = ciphertext + tag
41
+
42
+ decrypted = aesgcm.decrypt(iv, encrypted, None)
43
+
44
+ return decrypted.decode()
parameters.yaml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # actually n_pool = sum of n_pool for n_consumer , each consumer has pool oof min: max number of connections
2
+
3
+ min_n_pool : 2
4
+ max_n_pool : 10
5
+ n_consumer : 1
redis_utils.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+
4
+ import redis
5
+
6
+
7
+ def redis_send(redis_conn,user_id, chat_id, answer):
8
+ return redis_conn.set(f"ANSWER_FOR_USER_ID{user_id}_OF_{chat_id}", json.dumps(answer))
requirements.txt CHANGED
@@ -1,18 +1,27 @@
1
- langchain-huggingface
2
- langchain-core
3
- langchain-text-splitters
4
- langgraph
5
- python-dotenv
6
- pandas
7
- openpyxl
8
- sentence-transformers
9
- requests
10
- ipython
11
- fastapi
12
- uvicorn
13
- markdown_pdf
14
- pika
15
- redis
16
- cryptography
17
- sentry-sdk
18
- psutil
 
 
 
 
 
 
 
 
 
 
1
+ fastapi[standard]
2
+ arabic-reshaper==3.0.1
3
+ langchain==1.3.14
4
+ langchain-google-genai==4.3.2
5
+ langchain-huggingface==1.2.2
6
+ langchain-openai==1.4.1
7
+ langchain-text-splitters==1.1.2
8
+ langgraph==1.2.10
9
+ langgraph-checkpoint-postgres==3.1.1
10
+ python-bidi==0.6.11
11
+ reportlab==5.0.0
12
+ psycopg[binary]
13
+ psycopg[pool]
14
+ python-dotenv
15
+ pandas
16
+ openpyxl
17
+ sentence-transformers
18
+ requests
19
+ ipython
20
+ fastapi
21
+ uvicorn
22
+ markdown_pdf
23
+ pika
24
+ redis
25
+ cryptography
26
+ sentry-sdk
27
+ psutil
utils.py CHANGED
@@ -1,17 +1,33 @@
1
- from pydantic import BaseModel
2
-
3
-
4
- class RequestModel(BaseModel):
5
- prompt: str
6
- ht_token_encrypted_dumped : str
7
- user_email : str
8
- user_name : str
9
- user_id : str
10
- msg_id : str
11
- memory: list[str]
12
- last_state : str
13
-
14
- class RequestAnswer(BaseModel):
15
- user_id: str
16
- msg_id:str
17
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+ import os
3
+ import dotenv
4
+ import pika
5
+ current_dir = os.path.dirname(os.path.abspath(__file__)) # directory of main file utils.py ######## So This must be in app.py only
6
+
7
+
8
+ path_env = os.path.join(current_dir, "keys.env")
9
+ path_rag_file = os.path.join(current_dir,"agent","rag","rag.xlsx")
10
+ consumer_path = os.path.join(current_dir, "Consumer.py")
11
+ yaml_path = os.path.join(current_dir, "parameters.yaml")
12
+
13
+ dotenv.load_dotenv(path_env) #
14
+ RABBITMQ_URL = os.environ["RABBITMQ_URL"]
15
+
16
+
17
+ class RequestModel(BaseModel):
18
+ prompt: str
19
+ ht_token_encrypted_dumped : str
20
+ user_email : str
21
+ user_name : str
22
+ user_id : str
23
+ chat_id : str
24
+
25
+
26
+ class RequestAnswer(BaseModel):
27
+ user_id: str
28
+ chat_id:str
29
+
30
+
31
+ def get_rabbit_connection():
32
+ params = pika.URLParameters(RABBITMQ_URL)
33
+ return pika.BlockingConnection(params)