ibrahimlasfar commited on
Commit
2ee9112
·
1 Parent(s): 151b25b

Update chatbot with audio/image buttons and fixed models

Browse files
Files changed (5) hide show
  1. api/endpoints.py +22 -22
  2. api/models.py +1 -1
  3. main.py +45 -44
  4. utils/generation.py +38 -136
  5. utils/web_search.py +4 -15
api/endpoints.py CHANGED
@@ -1,22 +1,24 @@
1
  import os
2
  from fastapi import APIRouter, HTTPException, UploadFile, File
 
 
3
  from openai import OpenAI
4
  from api.models import QueryRequest
5
  from utils.generation import request_generation, select_model
6
- from utils.web_search import web_search
7
 
8
  router = APIRouter()
9
 
10
  HF_TOKEN = os.getenv("HF_TOKEN")
 
11
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://router.huggingface.co/v1")
12
- MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-20b:fireworks-ai")
13
 
14
  @router.get("/api/model-info")
15
  def model_info():
16
  return {
17
  "model_name": MODEL_NAME,
18
- "secondary_model": os.getenv("SECONDARY_MODEL_NAME", "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B"),
19
- "tertiary_model": os.getenv("TERTIARY_MODEL_NAME", "mistralai/Mixtral-8x7B-Instruct-v0.1"),
20
  "clip_base_model": os.getenv("CLIP_BASE_MODEL", "openai/clip-vit-base-patch32"),
21
  "clip_large_model": os.getenv("CLIP_LARGE_MODEL", "openai/clip-vit-large-patch14"),
22
  "api_base": API_ENDPOINT,
@@ -45,16 +47,14 @@ async def chat_endpoint(req: QueryRequest):
45
  max_new_tokens=req.max_new_tokens,
46
  deep_search=req.enable_browsing,
47
  )
48
- response = "".join(list(stream))
49
  return {"response": response}
50
 
51
-
52
- # في api/endpoints.py
53
  @router.post("/api/audio-transcription")
54
  async def audio_transcription_endpoint(file: UploadFile = File(...)):
55
  model_name, api_endpoint = select_model("transcribe audio", input_type="audio")
56
  audio_data = await file.read()
57
- response = "".join(list(request_generation(
58
  api_key=HF_TOKEN,
59
  api_base=api_endpoint,
60
  message="Transcribe audio",
@@ -64,7 +64,7 @@ async def audio_transcription_endpoint(file: UploadFile = File(...)):
64
  max_new_tokens=128000,
65
  input_type="audio",
66
  audio_data=audio_data,
67
- )))
68
  return {"transcription": response}
69
 
70
  @router.post("/api/text-to-speech")
@@ -81,7 +81,7 @@ async def text_to_speech_endpoint(req: dict):
81
  max_new_tokens=128000,
82
  input_type="text",
83
  )
84
- audio_data = b"".join(list(response))
85
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
86
 
87
  @router.post("/api/code")
@@ -91,7 +91,7 @@ async def code_endpoint(req: dict):
91
  code = req.get("code", "")
92
  prompt = f"Generate code for task: {task} using {framework}. Existing code: {code}"
93
  model_name, api_endpoint = select_model(prompt)
94
- response = "".join(list(request_generation(
95
  api_key=HF_TOKEN,
96
  api_base=api_endpoint,
97
  message=prompt,
@@ -99,14 +99,14 @@ async def code_endpoint(req: dict):
99
  model_name=model_name,
100
  temperature=0.7,
101
  max_new_tokens=128000,
102
- )))
103
  return {"generated_code": response}
104
 
105
  @router.post("/api/analysis")
106
  async def analysis_endpoint(req: dict):
107
  message = req.get("text", "")
108
  model_name, api_endpoint = select_model(message)
109
- response = "".join(list(request_generation(
110
  api_key=HF_TOKEN,
111
  api_base=api_endpoint,
112
  message=message,
@@ -114,24 +114,24 @@ async def analysis_endpoint(req: dict):
114
  model_name=model_name,
115
  temperature=0.7,
116
  max_new_tokens=128000,
117
- )))
118
  return {"analysis": response}
119
 
120
  @router.post("/api/image-analysis")
121
- async def image_analysis_endpoint(req: dict):
122
- image_url = req.get("image_url", "")
123
- task = req.get("task", "describe")
124
- prompt = f"Perform the following task on the image at {image_url}: {task}"
125
- model_name, api_endpoint = select_model(prompt)
126
- response = "".join(list(request_generation(
127
  api_key=HF_TOKEN,
128
  api_base=api_endpoint,
129
- message=prompt,
130
  system_prompt="You are an expert in image analysis. Provide detailed descriptions or classifications based on the query.",
131
  model_name=model_name,
132
  temperature=0.7,
133
  max_new_tokens=128000,
134
- )))
 
 
135
  return {"image_analysis": response}
136
 
137
  @router.get("/api/test-model")
 
1
  import os
2
  from fastapi import APIRouter, HTTPException, UploadFile, File
3
+ from fastapi.responses import StreamingResponse
4
+ import io
5
  from openai import OpenAI
6
  from api.models import QueryRequest
7
  from utils.generation import request_generation, select_model
 
8
 
9
  router = APIRouter()
10
 
11
  HF_TOKEN = os.getenv("HF_TOKEN")
12
+ BACKUP_HF_TOKEN = os.getenv("BACKUP_HF_TOKEN")
13
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://router.huggingface.co/v1")
14
+ MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-20b:together")
15
 
16
  @router.get("/api/model-info")
17
  def model_info():
18
  return {
19
  "model_name": MODEL_NAME,
20
+ "secondary_model": os.getenv("SECONDARY_MODEL_NAME", "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B:featherless-ai"),
21
+ "tertiary_model": os.getenv("TERTIARY_MODEL_NAME", "openai/gpt-oss-120b:cerebras"),
22
  "clip_base_model": os.getenv("CLIP_BASE_MODEL", "openai/clip-vit-base-patch32"),
23
  "clip_large_model": os.getenv("CLIP_LARGE_MODEL", "openai/clip-vit-large-patch14"),
24
  "api_base": API_ENDPOINT,
 
47
  max_new_tokens=req.max_new_tokens,
48
  deep_search=req.enable_browsing,
49
  )
50
+ response = "".join([chunk for chunk in stream if isinstance(chunk, str)])
51
  return {"response": response}
52
 
 
 
53
  @router.post("/api/audio-transcription")
54
  async def audio_transcription_endpoint(file: UploadFile = File(...)):
55
  model_name, api_endpoint = select_model("transcribe audio", input_type="audio")
56
  audio_data = await file.read()
57
+ response = "".join([chunk for chunk in request_generation(
58
  api_key=HF_TOKEN,
59
  api_base=api_endpoint,
60
  message="Transcribe audio",
 
64
  max_new_tokens=128000,
65
  input_type="audio",
66
  audio_data=audio_data,
67
+ ) if isinstance(chunk, str)])
68
  return {"transcription": response}
69
 
70
  @router.post("/api/text-to-speech")
 
81
  max_new_tokens=128000,
82
  input_type="text",
83
  )
84
+ audio_data = b"".join([chunk for chunk in response if isinstance(chunk, bytes)])
85
  return StreamingResponse(io.BytesIO(audio_data), media_type="audio/wav")
86
 
87
  @router.post("/api/code")
 
91
  code = req.get("code", "")
92
  prompt = f"Generate code for task: {task} using {framework}. Existing code: {code}"
93
  model_name, api_endpoint = select_model(prompt)
94
+ response = "".join([chunk for chunk in request_generation(
95
  api_key=HF_TOKEN,
96
  api_base=api_endpoint,
97
  message=prompt,
 
99
  model_name=model_name,
100
  temperature=0.7,
101
  max_new_tokens=128000,
102
+ ) if isinstance(chunk, str)])
103
  return {"generated_code": response}
104
 
105
  @router.post("/api/analysis")
106
  async def analysis_endpoint(req: dict):
107
  message = req.get("text", "")
108
  model_name, api_endpoint = select_model(message)
109
+ response = "".join([chunk for chunk in request_generation(
110
  api_key=HF_TOKEN,
111
  api_base=api_endpoint,
112
  message=message,
 
114
  model_name=model_name,
115
  temperature=0.7,
116
  max_new_tokens=128000,
117
+ ) if isinstance(chunk, str)])
118
  return {"analysis": response}
119
 
120
  @router.post("/api/image-analysis")
121
+ async def image_analysis_endpoint(file: UploadFile = File(...)):
122
+ model_name, api_endpoint = select_model("analyze image", input_type="image")
123
+ image_data = await file.read()
124
+ response = "".join([chunk for chunk in request_generation(
 
 
125
  api_key=HF_TOKEN,
126
  api_base=api_endpoint,
127
+ message="Analyze this image",
128
  system_prompt="You are an expert in image analysis. Provide detailed descriptions or classifications based on the query.",
129
  model_name=model_name,
130
  temperature=0.7,
131
  max_new_tokens=128000,
132
+ input_type="image",
133
+ image_data=image_data,
134
+ ) if isinstance(chunk, str)])
135
  return {"image_analysis": response}
136
 
137
  @router.get("/api/test-model")
api/models.py CHANGED
@@ -3,7 +3,7 @@ from typing import List, Optional
3
 
4
  class QueryRequest(BaseModel):
5
  message: str
6
- system_prompt: str = "You are an expert assistant providing detailed, comprehensive, and well-structured responses. Support text, audio, and image inputs. Transcribe audio using Whisper, convert text to speech using Parler-TTS, and analyze images using CLIP. Respond with text or audio based on input type. Continue until the query is fully addressed."
7
  history: Optional[List[dict]] = None
8
  temperature: float = 0.7
9
  max_new_tokens: int = 128000
 
3
 
4
  class QueryRequest(BaseModel):
5
  message: str
6
+ system_prompt: str = "You are an expert assistant providing detailed, comprehensive, and well-structured responses. Support text, audio, image inputs. For audio, transcribe using Whisper. For text-to-speech, use Parler-TTS. For images, analyze using CLIP. Respond with voice output when requested. Continue until the query is fully addressed."
7
  history: Optional[List[dict]] = None
8
  temperature: float = 0.7
9
  max_new_tokens: int = 128000
main.py CHANGED
@@ -29,32 +29,29 @@ if not HF_TOKEN:
29
  QUEUE_SIZE = int(os.getenv("QUEUE_SIZE", 80))
30
  CONCURRENCY_LIMIT = int(os.getenv("CONCURRENCY_LIMIT", 20))
31
 
32
- # إعداد CSS محسّن
33
  css = """
34
  .gradio-container { max-width: 1200px; margin: auto; font-family: Arial, sans-serif; }
35
  .chatbot { border: 1px solid #ccc; border-radius: 12px; padding: 20px; background-color: #f5f5f5; }
36
  .input-textbox { font-size: 16px; padding: 12px; border-radius: 8px; }
37
- .upload-button, .audio-button, .camera-button {
38
- background-color: #007bff; color: white; padding: 10px 20px; border-radius: 8px;
39
- display: inline-flex; align-items: center; gap: 8px; font-size: 16px;
40
  }
41
- .upload-button::before { content: '📷'; font-size: 20px; }
42
- .audio-button::before { content: '🎤'; font-size: 20px; }
43
- .camera-button::before { content: '📸'; font-size: 20px; }
44
- .audio-output-container {
45
- display: flex; align-items: center; gap: 12px; margin-top: 15px;
46
- background-color: #e9ecef; padding: 10px; border-radius: 8px;
47
- }
48
- .audio-output-container::before { content: '🔊'; font-size: 20px; }
49
  .loading::after {
50
- content: ''; display: inline-block; width: 18px; height: 18px;
51
- border: 3px solid #007bff; border-top-color: transparent;
52
- border-radius: 50%; animation: spin 1s linear infinite; margin-left: 10px;
53
  }
54
  @keyframes spin { to { transform: rotate(360deg); } }
55
- .output-container {
56
- margin-top: 20px; padding: 15px; border: 1px solid #ddd;
57
- border-radius: 10px; background-color: white;
 
 
58
  }
59
  """
60
 
@@ -70,7 +67,7 @@ def process_input(message, audio_input=None, image_input=None, history=None, sys
70
  elif image_input:
71
  input_type = "image"
72
  image_data = image_input
73
- message = f"Analyze image: {message or 'describe this image'}"
74
 
75
  response_text = ""
76
  audio_response = None
@@ -93,47 +90,56 @@ def process_input(message, audio_input=None, image_input=None, history=None, sys
93
  response_text += chunk
94
  yield response_text, audio_response
95
 
 
 
 
 
 
 
 
 
96
  # إعداد واجهة Gradio
97
- chatbot_ui = gr.ChatInterface(
98
  fn=process_input,
99
- chatbot=gr.Chatbot(
100
- label="MGZon Chatbot",
101
- height=800,
102
- latex_delimiters=LATEX_DELIMS,
103
- elem_classes="chatbot",
104
- ),
105
- additional_inputs_accordion=gr.Accordion("⚙️ Settings", open=True),
106
- additional_inputs=[
107
  gr.Textbox(
108
  label="System Prompt",
109
- value="You are an expert assistant providing detailed, comprehensive, and well-structured responses. Support text, audio, image inputs. Transcribe audio using Whisper, convert text to speech using Parler-TTS, and analyze images using CLIP. Respond with text or audio based on input type. Continue until the query is fully addressed.",
110
  lines=4
111
  ),
112
  gr.Slider(label="Temperature", minimum=0.0, maximum=1.0, step=0.1, value=0.7),
113
  gr.Radio(label="Reasoning Effort", choices=["low", "medium", "high"], value="medium"),
114
  gr.Checkbox(label="Enable DeepSearch", value=True),
115
  gr.Slider(label="Max New Tokens", minimum=50, maximum=128000, step=50, value=128000),
116
- gr.Audio(label="Record Audio", source="microphone", type="numpy", elem_classes="audio-button"),
117
- gr.Image(label="Capture Image", source="webcam", type="numpy", elem_classes="camera-button"),
118
- gr.File(label="Upload Image/File", file_types=["image", ".pdf", ".txt"], elem_classes="upload-button"),
119
  ],
120
- additional_outputs=[gr.Audio(label="Voice Output", type="filepath", elem_classes="audio-output-container", autoplay=True)],
121
- stop_btn="Stop",
 
 
 
 
 
 
122
  examples=[
123
  ["Explain the history of AI in detail."],
124
- ["Generate a React login component with validation."],
125
- ["Describe this image: [capture or upload image]"],
126
- ["Transcribe this audio: [record audio]"],
127
- ["Convert to speech: Hello, welcome to MGZon!"],
128
  ],
129
  title="MGZon Chatbot",
130
- description="A versatile chatbot powered by Hugging Face models for text, image, and audio queries. Supports real-time audio recording, webcam image capture, and web search. Licensed under Apache 2.0.",
131
  theme="gradio/soft",
132
  css=css,
133
  )
134
 
135
  # إعداد FastAPI
136
  app = FastAPI(title="MGZon Chatbot API")
 
137
 
138
  # ربط Gradio مع FastAPI
139
  app = gr.mount_gradio_app(app, chatbot_ui, path="/gradio")
@@ -157,27 +163,22 @@ class NotFoundMiddleware(BaseHTTPMiddleware):
157
 
158
  app.add_middleware(NotFoundMiddleware)
159
 
160
- # Root endpoint
161
  @app.get("/", response_class=HTMLResponse)
162
  async def root(request: Request):
163
  return templates.TemplateResponse("index.html", {"request": request})
164
 
165
- # Docs endpoint
166
  @app.get("/docs", response_class=HTMLResponse)
167
  async def docs(request: Request):
168
  return templates.TemplateResponse("docs.html", {"request": request})
169
 
170
- # Swagger UI endpoint
171
  @app.get("/swagger", response_class=HTMLResponse)
172
  async def swagger_ui():
173
  return get_swagger_ui_html(openapi_url="/openapi.json", title="MGZon API Documentation")
174
 
175
- # Redirect لـ /gradio
176
  @app.get("/launch-chatbot", response_class=RedirectResponse)
177
  async def launch_chatbot():
178
  return RedirectResponse(url="/gradio", status_code=302)
179
 
180
- # تشغيل الخادم
181
  if __name__ == "__main__":
182
  import uvicorn
183
  uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
 
29
  QUEUE_SIZE = int(os.getenv("QUEUE_SIZE", 80))
30
  CONCURRENCY_LIMIT = int(os.getenv("CONCURRENCY_LIMIT", 20))
31
 
32
+ # إعداد CSS
33
  css = """
34
  .gradio-container { max-width: 1200px; margin: auto; font-family: Arial, sans-serif; }
35
  .chatbot { border: 1px solid #ccc; border-radius: 12px; padding: 20px; background-color: #f5f5f5; }
36
  .input-textbox { font-size: 16px; padding: 12px; border-radius: 8px; }
37
+ .upload-button, .capture-button, .record-button {
38
+ background-color: #4CAF50; color: white; padding: 10px 20px; border-radius: 8px; font-size: 16px; cursor: pointer;
 
39
  }
40
+ .upload-button:hover, .capture-button:hover, .record-button:hover { background-color: #45a049; }
41
+ .upload-button::before { content: '📷 '; font-size: 20px; }
42
+ .capture-button::before { content: '🎥 '; font-size: 20px; }
43
+ .record-button::before { content: '🎤 '; font-size: 20px; }
44
+ .audio-output::before { content: '🔊 '; font-size: 20px; }
 
 
 
45
  .loading::after {
46
+ content: ''; display: inline-block; width: 18px; height: 18px; border: 3px solid #333;
47
+ border-top-color: transparent; border-radius: 50%; animation: spin 1s linear infinite; margin-left: 10px;
 
48
  }
49
  @keyframes spin { to { transform: rotate(360deg); } }
50
+ .output-container {
51
+ margin-top: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 10px; background-color: #fff;
52
+ }
53
+ .audio-output-container {
54
+ display: flex; align-items: center; gap: 12px; margin-top: 15px;
55
  }
56
  """
57
 
 
67
  elif image_input:
68
  input_type = "image"
69
  image_data = image_input
70
+ message = "Analyze this image"
71
 
72
  response_text = ""
73
  audio_response = None
 
90
  response_text += chunk
91
  yield response_text, audio_response
92
 
93
+ # دالة لتفعيل تسجيل الصوت
94
+ def start_recording():
95
+ return gr.update(visible=True)
96
+
97
+ # دالة لتفعيل التقاط الصورة
98
+ def start_image_capture():
99
+ return gr.update(visible=True)
100
+
101
  # إعداد واجهة Gradio
102
+ chatbot_ui = gr.Interface(
103
  fn=process_input,
104
+ inputs=[
105
+ gr.Textbox(label="Message", placeholder="Type your message or use buttons below...", elem_classes="input-textbox"),
106
+ gr.Audio(label="Record Audio", sources=["microphone"], type="numpy", streaming=True, visible=False, elem_classes="record-button"),
107
+ gr.Image(label="Capture/Upload Image", sources=["webcam", "upload"], type="numpy", visible=False, elem_classes="capture-button"),
108
+ gr.State(value=[]), # History
 
 
 
109
  gr.Textbox(
110
  label="System Prompt",
111
+ value="You are an expert assistant providing detailed, comprehensive, and well-structured responses. Support text, audio, image inputs. For audio, transcribe using Whisper. For text-to-speech, use Parler-TTS. For images, analyze using CLIP. Respond with voice output when requested. Continue until the query is fully addressed.",
112
  lines=4
113
  ),
114
  gr.Slider(label="Temperature", minimum=0.0, maximum=1.0, step=0.1, value=0.7),
115
  gr.Radio(label="Reasoning Effort", choices=["low", "medium", "high"], value="medium"),
116
  gr.Checkbox(label="Enable DeepSearch", value=True),
117
  gr.Slider(label="Max New Tokens", minimum=50, maximum=128000, step=50, value=128000),
 
 
 
118
  ],
119
+ outputs=[
120
+ gr.Markdown(label="Response", elem_classes="output-container"),
121
+ gr.Audio(label="Voice Output", type="filepath", elem_classes="audio-output", autoplay=True)
122
+ ],
123
+ additional_inputs=[
124
+ gr.Button("Record Audio", elem_classes="record-button", onclick=start_recording),
125
+ gr.Button("Capture/Upload Image", elem_classes="capture-button", onclick=start_image_capture),
126
+ ],
127
  examples=[
128
  ["Explain the history of AI in detail."],
129
+ ["Generate a React component for a login form."],
130
+ ["Transcribe this audio: [record audio]."],
131
+ ["Convert this text to speech: Hello, welcome to MGZon!"],
132
+ ["Analyze this image: [capture/upload image]."],
133
  ],
134
  title="MGZon Chatbot",
135
+ description="A versatile chatbot powered by advanced AI models. Supports text, audio, and image inputs with voice responses. Licensed under Apache 2.0.",
136
  theme="gradio/soft",
137
  css=css,
138
  )
139
 
140
  # إعداد FastAPI
141
  app = FastAPI(title="MGZon Chatbot API")
142
+ app.include_router(api_router)
143
 
144
  # ربط Gradio مع FastAPI
145
  app = gr.mount_gradio_app(app, chatbot_ui, path="/gradio")
 
163
 
164
  app.add_middleware(NotFoundMiddleware)
165
 
 
166
  @app.get("/", response_class=HTMLResponse)
167
  async def root(request: Request):
168
  return templates.TemplateResponse("index.html", {"request": request})
169
 
 
170
  @app.get("/docs", response_class=HTMLResponse)
171
  async def docs(request: Request):
172
  return templates.TemplateResponse("docs.html", {"request": request})
173
 
 
174
  @app.get("/swagger", response_class=HTMLResponse)
175
  async def swagger_ui():
176
  return get_swagger_ui_html(openapi_url="/openapi.json", title="MGZon API Documentation")
177
 
 
178
  @app.get("/launch-chatbot", response_class=RedirectResponse)
179
  async def launch_chatbot():
180
  return RedirectResponse(url="/gradio", status_code=302)
181
 
 
182
  if __name__ == "__main__":
183
  import uvicorn
184
  uvicorn.run(app, host="0.0.0.0", port=int(os.getenv("PORT", 7860)))
utils/generation.py CHANGED
@@ -15,11 +15,12 @@ import torchaudio
15
  from PIL import Image
16
  from transformers import CLIPModel, CLIPProcessor, AutoProcessor
17
  from parler_tts import ParlerTTSForConditionalGeneration
 
18
 
19
  logger = logging.getLogger(__name__)
20
 
21
  # إعداد Cache
22
- cache = TTLCache(maxsize=100, ttl=600) # Cache بحجم 100 ومدة 10 دقايق
23
 
24
  # تعريف LATEX_DELIMS
25
  LATEX_DELIMS = [
@@ -31,19 +32,18 @@ LATEX_DELIMS = [
31
 
32
  # إعداد العميل لـ Hugging Face Inference API
33
  HF_TOKEN = os.getenv("HF_TOKEN")
34
- BACKUP_HF_TOKEN = os.getenv("BACKUP_HF_TOKEN") # توكن احتياطي
35
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://router.huggingface.co/v1")
36
  FALLBACK_API_ENDPOINT = "https://api-inference.huggingface.co/v1"
37
- MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-20b:fireworks-ai")
38
- SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B")
39
- TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "mistralai/Mixtral-8x7B-Instruct-v0.1")
40
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "openai/clip-vit-base-patch32")
41
  CLIP_LARGE_MODEL = os.getenv("CLIP_LARGE_MODEL", "openai/clip-vit-large-patch14")
42
  ASR_MODEL = os.getenv("ASR_MODEL", "openai/whisper-large-v3-turbo")
43
  TTS_MODEL = os.getenv("TTS_MODEL", "parler-tts/parler-tts-mini-v1")
44
 
45
  def check_model_availability(model_name: str, api_base: str, api_key: str) -> tuple[bool, str]:
46
- """التحقق من توفر النموذج عبر API مع دعم التوكن الاحتياطي"""
47
  try:
48
  response = requests.get(
49
  f"{api_base}/models/{model_name}",
@@ -66,33 +66,18 @@ def check_model_availability(model_name: str, api_base: str, api_key: str) -> tu
66
 
67
  def select_model(query: str, input_type: str = "text") -> tuple[str, str]:
68
  query_lower = query.lower()
69
- # دعم الصوت
70
  if input_type == "audio" or any(keyword in query_lower for keyword in ["voice", "audio", "speech", "صوت", "تحويل صوت"]):
71
  logger.info(f"Selected {ASR_MODEL} with endpoint {FALLBACK_API_ENDPOINT} for audio input")
72
  return ASR_MODEL, FALLBACK_API_ENDPOINT
73
- # دعم تحويل النص إلى صوت
74
  if any(keyword in query_lower for keyword in ["text-to-speech", "tts", "تحويل نص إلى صوت"]):
75
  logger.info(f"Selected {TTS_MODEL} with endpoint {FALLBACK_API_ENDPOINT} for text-to-speech")
76
  return TTS_MODEL, FALLBACK_API_ENDPOINT
77
- # نماذج CLIP للاستعلامات المتعلقة بالصور
78
- image_patterns = [
79
  r"\bimage\b", r"\bpicture\b", r"\bphoto\b", r"\bvisual\b", r"\bصورة\b", r"\bتحليل\s+صورة\b",
80
  r"\bimage\s+analysis\b", r"\bimage\s+classification\b", r"\bimage\s+description\b"
81
- ]
82
- for pattern in image_patterns:
83
- if re.search(pattern, query_lower, re.IGNORECASE):
84
- logger.info(f"Selected {CLIP_BASE_MODEL} with endpoint {FALLBACK_API_ENDPOINT} for image-related query: {query}")
85
- return CLIP_BASE_MODEL, FALLBACK_API_ENDPOINT
86
- # نموذج DeepSeek للاستعلامات المتعلقة بـ MGZon
87
- mgzon_patterns = [
88
- r"\bmgzon\b", r"\bmgzon\s+(products|services|platform|features|mission|technology|solutions|oauth)\b",
89
- r"\bميزات\s+mgzon\b", r"\bخدمات\s+mgzon\b", r"\boauth\b"
90
- ]
91
- for pattern in mgzon_patterns:
92
- if re.search(pattern, query_lower, re.IGNORECASE):
93
- logger.info(f"Selected {SECONDARY_MODEL_NAME} with endpoint {FALLBACK_API_ENDPOINT} for MGZon-related query: {query}")
94
- return SECONDARY_MODEL_NAME, FALLBACK_API_ENDPOINT
95
- # النموذج الافتراضي للاستعلامات العامة
96
  logger.info(f"Selected {MODEL_NAME} with endpoint {API_ENDPOINT} for general query: {query}")
97
  return MODEL_NAME, API_ENDPOINT
98
 
@@ -114,15 +99,11 @@ def request_generation(
114
  audio_data: Optional[bytes] = None,
115
  image_data: Optional[bytes] = None,
116
  ) -> Generator[bytes | str, None, None]:
117
- from utils.web_search import web_search # تأخير الاستيراد
118
-
119
- # التحقق من توفر النموذج مع دعم التوكن الاحتياطي
120
  is_available, selected_api_key = check_model_availability(model_name, api_base, api_key)
121
  if not is_available:
122
  yield f"Error: Model {model_name} is not available. Please check the model endpoint or token."
123
  return
124
 
125
- # إنشاء مفتاح للـ cache
126
  cache_key = hashlib.md5(json.dumps({
127
  "message": message,
128
  "system_prompt": system_prompt,
@@ -142,8 +123,7 @@ def request_generation(
142
  task_type = "general"
143
  enhanced_system_prompt = system_prompt
144
 
145
- # معالجة الصوت (ASR)
146
- if model_name == ASR_MODEL and audio_data:
147
  task_type = "audio_transcription"
148
  try:
149
  audio_file = io.BytesIO(audio_data)
@@ -165,12 +145,11 @@ def request_generation(
165
  yield f"Error: Audio transcription failed: {e}"
166
  return
167
 
168
- # معالجة تحويل النص إلى صوت (TTS)
169
  if model_name == TTS_MODEL:
170
  task_type = "text_to_speech"
171
  try:
172
- model = ParlerTTSForConditionalGeneration.from_pretrained(model_name)
173
- processor = AutoProcessor.from_pretrained(model_name)
174
  inputs = processor(text=message, return_tensors="pt")
175
  audio = model.generate(**inputs)
176
  audio_file = io.BytesIO()
@@ -184,12 +163,11 @@ def request_generation(
184
  yield f"Error: Text-to-speech failed: {e}"
185
  return
186
 
187
- # معالجة الصور
188
- if model_name in [CLIP_BASE_MODEL, CLIP_LARGE_MODEL] and image_data:
189
  task_type = "image_analysis"
190
  try:
191
- model = CLIPModel.from_pretrained(model_name)
192
- processor = CLIPProcessor.from_pretrained(model_name)
193
  image = Image.open(io.BytesIO(image_data)).convert("RGB")
194
  inputs = processor(text=message, images=image, return_tensors="pt", padding=True)
195
  outputs = model(**inputs)
@@ -203,28 +181,26 @@ def request_generation(
203
  yield f"Error: Image analysis failed: {e}"
204
  return
205
 
206
- # تحسين system_prompt بناءً على نوع المهمة
207
  if model_name in [CLIP_BASE_MODEL, CLIP_LARGE_MODEL]:
208
  task_type = "image"
209
- enhanced_system_prompt = f"{system_prompt}\nYou are an expert in image analysis and description. Provide detailed descriptions, classifications, or analysis of images based on the query. Continue until the query is fully addressed."
210
  elif any(keyword in message.lower() for keyword in ["code", "programming", "python", "javascript", "react", "django", "flask"]):
211
  task_type = "code"
212
- enhanced_system_prompt = f"{system_prompt}\nYou are an expert programmer. Provide accurate, well-commented code with comprehensive examples and detailed explanations. Support frameworks like React, Django, Flask, and others. Format code with triple backticks (```) and specify the language. Continue until the task is fully addressed."
213
  elif any(keyword in message.lower() for keyword in ["analyze", "analysis", "تحليل"]):
214
  task_type = "analysis"
215
- enhanced_system_prompt = f"{system_prompt}\nProvide detailed analysis with step-by-step reasoning, examples, and data-driven insights. Continue until all aspects of the query are thoroughly covered."
216
  elif any(keyword in message.lower() for keyword in ["review", "مراجعة"]):
217
  task_type = "review"
218
- enhanced_system_prompt = f"{system_prompt}\nReview the provided content thoroughly, identify issues, and suggest improvements with detailed explanations. Ensure the response is complete and detailed."
219
  elif any(keyword in message.lower() for keyword in ["publish", "نشر"]):
220
  task_type = "publish"
221
- enhanced_system_prompt = f"{system_prompt}\nPrepare content for publishing, ensuring clarity, professionalism, and adherence to best practices. Provide a complete and detailed response."
222
  else:
223
- enhanced_system_prompt = f"{system_prompt}\nFor general queries, provide comprehensive, detailed responses with examples and explanations where applicable. Continue generating content until the query is fully answered, leveraging the full capacity of the model."
224
 
225
- # إذا كان الاستعلام قصيرًا، شجع على التفصيل
226
  if len(message.split()) < 5:
227
- enhanced_system_prompt += "\nEven for short or general queries, provide a detailed, in-depth response with examples, explanations, and additional context to ensure completeness."
228
 
229
  logger.info(f"Task type detected: {task_type}")
230
  input_messages: List[dict] = [{"role": "system", "content": enhanced_system_prompt}]
@@ -313,7 +289,7 @@ def request_generation(
313
  reasoning_closed = True
314
 
315
  if not saw_visible_output:
316
- msg = "I attempted to call a tool, but tools aren't executed in this environment, so no final answer was produced."
317
  if last_tool_name:
318
  try:
319
  args_text = json.dumps(last_tool_args, ensure_ascii=False, default=str)
@@ -327,8 +303,8 @@ def request_generation(
327
  cached_chunks.append(f"Error: Unknown error")
328
  yield f"Error: Unknown error"
329
  elif chunk.choices[0].finish_reason == "length":
330
- cached_chunks.append("Response truncated due to token limit. Please refine your query or request continuation.")
331
- yield "Response truncated due to token limit. Please refine your query or request continuation."
332
  break
333
 
334
  if buffer:
@@ -360,16 +336,13 @@ def request_generation(
360
  ):
361
  yield chunk
362
  return
363
- if model_name == MODEL_NAME:
364
- fallback_model = SECONDARY_MODEL_NAME
365
- fallback_endpoint = FALLBACK_API_ENDPOINT
366
- logger.info(f"Retrying with fallback model: {fallback_model} on {fallback_endpoint}")
367
  try:
368
- is_available, selected_api_key = check_model_availability(fallback_model, fallback_endpoint, selected_api_key)
369
  if not is_available:
370
- yield f"Error: Fallback model {fallback_model} is not available."
371
- return
372
- client = OpenAI(api_key=selected_api_key, base_url=fallback_endpoint, timeout=120.0)
373
  stream = client.chat.completions.create(
374
  model=fallback_model,
375
  messages=input_messages,
@@ -382,39 +355,18 @@ def request_generation(
382
  for chunk in stream:
383
  if chunk.choices[0].delta.content:
384
  content = chunk.choices[0].delta.content
385
- if content == "<|channel|>analysis<|message|>":
386
- if not reasoning_started:
387
- cached_chunks.append("analysis")
388
- yield "analysis"
389
- reasoning_started = True
390
- continue
391
- if content == "<|channel|>final<|message|>":
392
- if reasoning_started and not reasoning_closed:
393
- cached_chunks.append("assistantfinal")
394
- yield "assistantfinal"
395
- reasoning_closed = True
396
- continue
397
-
398
  saw_visible_output = True
399
  buffer += content
400
-
401
  if "\n" in buffer or len(buffer) > 5000:
402
  cached_chunks.append(buffer)
403
  yield buffer
404
  buffer = ""
405
  continue
406
-
407
  if chunk.choices[0].finish_reason in ("stop", "error", "length"):
408
  if buffer:
409
  cached_chunks.append(buffer)
410
  yield buffer
411
  buffer = ""
412
-
413
- if reasoning_started and not reasoning_closed:
414
- cached_chunks.append("assistantfinal")
415
- yield "assistantfinal"
416
- reasoning_closed = True
417
-
418
  if not saw_visible_output:
419
  cached_chunks.append("No visible output produced.")
420
  yield "No visible output produced."
@@ -422,69 +374,19 @@ def request_generation(
422
  cached_chunks.append(f"Error: Unknown error with fallback model {fallback_model}")
423
  yield f"Error: Unknown error with fallback model {fallback_model}"
424
  elif chunk.choices[0].finish_reason == "length":
425
- cached_chunks.append("Response truncated due to token limit. Please refine your query or request continuation.")
426
- yield "Response truncated due to token limit. Please refine your query or request continuation."
427
  break
428
-
429
  if buffer:
430
  cached_chunks.append(buffer)
431
  yield buffer
432
-
433
  cache[cache_key] = cached_chunks
434
-
435
  except Exception as e2:
436
  logger.exception(f"[Gateway] Streaming failed for fallback model {fallback_model}: {e2}")
437
- try:
438
- is_available, selected_api_key = check_model_availability(TERTIARY_MODEL_NAME, FALLBACK_API_ENDPOINT, selected_api_key)
439
- if not is_available:
440
- yield f"Error: Tertiary model {TERTIARY_MODEL_NAME} is not available."
441
- return
442
- client = OpenAI(api_key=selected_api_key, base_url=FALLBACK_API_ENDPOINT, timeout=120.0)
443
- stream = client.chat.completions.create(
444
- model=TERTIARY_MODEL_NAME,
445
- messages=input_messages,
446
- temperature=temperature,
447
- max_tokens=max_new_tokens,
448
- stream=True,
449
- tools=[],
450
- tool_choice="none",
451
- )
452
- for chunk in stream:
453
- if chunk.choices[0].delta.content:
454
- content = chunk.choices[0].delta.content
455
- saw_visible_output = True
456
- buffer += content
457
- if "\n" in buffer or len(buffer) > 5000:
458
- cached_chunks.append(buffer)
459
- yield buffer
460
- buffer = ""
461
- continue
462
- if chunk.choices[0].finish_reason in ("stop", "error", "length"):
463
- if buffer:
464
- cached_chunks.append(buffer)
465
- yield buffer
466
- buffer = ""
467
- if not saw_visible_output:
468
- cached_chunks.append("No visible output produced.")
469
- yield "No visible output produced."
470
- if chunk.choices[0].finish_reason == "error":
471
- cached_chunks.append(f"Error: Unknown error with tertiary model {TERTIARY_MODEL_NAME}")
472
- yield f"Error: Unknown error with tertiary model {TERTIARY_MODEL_NAME}"
473
- elif chunk.choices[0].finish_reason == "length":
474
- cached_chunks.append("Response truncated due to token limit. Please refine your query or request continuation.")
475
- yield "Response truncated due to token limit. Please refine your query or request continuation."
476
- break
477
- if buffer:
478
- cached_chunks.append(buffer)
479
- yield buffer
480
- cache[cache_key] = cached_chunks
481
- except Exception as e3:
482
- logger.exception(f"[Gateway] Streaming failed for tertiary model {TERTIARY_MODEL_NAME}: {e3}")
483
- yield f"Error: Failed to load all models: Primary ({model_name}), Secondary ({fallback_model}), Tertiary ({TERTIARY_MODEL_NAME}). Please check your model configurations."
484
- return
485
- else:
486
- yield f"Error: Failed to load model {model_name}: {e}"
487
- return
488
 
489
  def format_final(analysis_text: str, visible_text: str) -> str:
490
  reasoning_safe = html.escape((analysis_text or "").strip())
@@ -534,7 +436,7 @@ def generate(message, history, system_prompt, temperature, reasoning_effort, ena
534
  "type": "function",
535
  "function": {
536
  "name": "code_generation",
537
- "description": "Generate or modify code for various frameworks (React, Django, Flask, etc.)",
538
  "parameters": {
539
  "type": "object",
540
  "properties": {
 
15
  from PIL import Image
16
  from transformers import CLIPModel, CLIPProcessor, AutoProcessor
17
  from parler_tts import ParlerTTSForConditionalGeneration
18
+ from utils.web_search import web_search # استيراد مباشر
19
 
20
  logger = logging.getLogger(__name__)
21
 
22
  # إعداد Cache
23
+ cache = TTLCache(maxsize=100, ttl=600)
24
 
25
  # تعريف LATEX_DELIMS
26
  LATEX_DELIMS = [
 
32
 
33
  # إعداد العميل لـ Hugging Face Inference API
34
  HF_TOKEN = os.getenv("HF_TOKEN")
35
+ BACKUP_HF_TOKEN = os.getenv("BACKUP_HF_TOKEN")
36
  API_ENDPOINT = os.getenv("API_ENDPOINT", "https://router.huggingface.co/v1")
37
  FALLBACK_API_ENDPOINT = "https://api-inference.huggingface.co/v1"
38
+ MODEL_NAME = os.getenv("MODEL_NAME", "openai/gpt-oss-20b:together")
39
+ SECONDARY_MODEL_NAME = os.getenv("SECONDARY_MODEL_NAME", "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B:featherless-ai")
40
+ TERTIARY_MODEL_NAME = os.getenv("TERTIARY_MODEL_NAME", "openai/gpt-oss-120b:cerebras")
41
  CLIP_BASE_MODEL = os.getenv("CLIP_BASE_MODEL", "openai/clip-vit-base-patch32")
42
  CLIP_LARGE_MODEL = os.getenv("CLIP_LARGE_MODEL", "openai/clip-vit-large-patch14")
43
  ASR_MODEL = os.getenv("ASR_MODEL", "openai/whisper-large-v3-turbo")
44
  TTS_MODEL = os.getenv("TTS_MODEL", "parler-tts/parler-tts-mini-v1")
45
 
46
  def check_model_availability(model_name: str, api_base: str, api_key: str) -> tuple[bool, str]:
 
47
  try:
48
  response = requests.get(
49
  f"{api_base}/models/{model_name}",
 
66
 
67
  def select_model(query: str, input_type: str = "text") -> tuple[str, str]:
68
  query_lower = query.lower()
 
69
  if input_type == "audio" or any(keyword in query_lower for keyword in ["voice", "audio", "speech", "صوت", "تحويل صوت"]):
70
  logger.info(f"Selected {ASR_MODEL} with endpoint {FALLBACK_API_ENDPOINT} for audio input")
71
  return ASR_MODEL, FALLBACK_API_ENDPOINT
 
72
  if any(keyword in query_lower for keyword in ["text-to-speech", "tts", "تحويل نص إلى صوت"]):
73
  logger.info(f"Selected {TTS_MODEL} with endpoint {FALLBACK_API_ENDPOINT} for text-to-speech")
74
  return TTS_MODEL, FALLBACK_API_ENDPOINT
75
+ if input_type == "image" or any(pattern in query_lower for pattern in [
 
76
  r"\bimage\b", r"\bpicture\b", r"\bphoto\b", r"\bvisual\b", r"\bصورة\b", r"\bتحليل\s+صورة\b",
77
  r"\bimage\s+analysis\b", r"\bimage\s+classification\b", r"\bimage\s+description\b"
78
+ ]):
79
+ logger.info(f"Selected {CLIP_BASE_MODEL} with endpoint {FALLBACK_API_ENDPOINT} for image-related query: {query}")
80
+ return CLIP_BASE_MODEL, FALLBACK_API_ENDPOINT
 
 
 
 
 
 
 
 
 
 
 
 
81
  logger.info(f"Selected {MODEL_NAME} with endpoint {API_ENDPOINT} for general query: {query}")
82
  return MODEL_NAME, API_ENDPOINT
83
 
 
99
  audio_data: Optional[bytes] = None,
100
  image_data: Optional[bytes] = None,
101
  ) -> Generator[bytes | str, None, None]:
 
 
 
102
  is_available, selected_api_key = check_model_availability(model_name, api_base, api_key)
103
  if not is_available:
104
  yield f"Error: Model {model_name} is not available. Please check the model endpoint or token."
105
  return
106
 
 
107
  cache_key = hashlib.md5(json.dumps({
108
  "message": message,
109
  "system_prompt": system_prompt,
 
123
  task_type = "general"
124
  enhanced_system_prompt = system_prompt
125
 
126
+ if model_name == ASR_MODEL and audio_data is not None:
 
127
  task_type = "audio_transcription"
128
  try:
129
  audio_file = io.BytesIO(audio_data)
 
145
  yield f"Error: Audio transcription failed: {e}"
146
  return
147
 
 
148
  if model_name == TTS_MODEL:
149
  task_type = "text_to_speech"
150
  try:
151
+ model = ParlerTTSForConditionalGeneration.from_pretrained(model_name, token=selected_api_key)
152
+ processor = AutoProcessor.from_pretrained(model_name, token=selected_api_key)
153
  inputs = processor(text=message, return_tensors="pt")
154
  audio = model.generate(**inputs)
155
  audio_file = io.BytesIO()
 
163
  yield f"Error: Text-to-speech failed: {e}"
164
  return
165
 
166
+ if model_name in [CLIP_BASE_MODEL, CLIP_LARGE_MODEL] and image_data is not None:
 
167
  task_type = "image_analysis"
168
  try:
169
+ model = CLIPModel.from_pretrained(model_name, token=selected_api_key)
170
+ processor = CLIPProcessor.from_pretrained(model_name, token=selected_api_key)
171
  image = Image.open(io.BytesIO(image_data)).convert("RGB")
172
  inputs = processor(text=message, images=image, return_tensors="pt", padding=True)
173
  outputs = model(**inputs)
 
181
  yield f"Error: Image analysis failed: {e}"
182
  return
183
 
 
184
  if model_name in [CLIP_BASE_MODEL, CLIP_LARGE_MODEL]:
185
  task_type = "image"
186
+ enhanced_system_prompt = f"{system_prompt}\nYou are an expert in image analysis and description. Provide detailed descriptions, classifications, or analysis of images based on the query."
187
  elif any(keyword in message.lower() for keyword in ["code", "programming", "python", "javascript", "react", "django", "flask"]):
188
  task_type = "code"
189
+ enhanced_system_prompt = f"{system_prompt}\nYou are an expert programmer. Provide accurate, well-commented code with comprehensive examples and detailed explanations."
190
  elif any(keyword in message.lower() for keyword in ["analyze", "analysis", "تحليل"]):
191
  task_type = "analysis"
192
+ enhanced_system_prompt = f"{system_prompt}\nProvide detailed analysis with step-by-step reasoning, examples, and data-driven insights."
193
  elif any(keyword in message.lower() for keyword in ["review", "مراجعة"]):
194
  task_type = "review"
195
+ enhanced_system_prompt = f"{system_prompt}\nReview the provided content thoroughly, identify issues, and suggest improvements with detailed explanations."
196
  elif any(keyword in message.lower() for keyword in ["publish", "نشر"]):
197
  task_type = "publish"
198
+ enhanced_system_prompt = f"{system_prompt}\nPrepare content for publishing, ensuring clarity, professionalism, and adherence to best practices."
199
  else:
200
+ enhanced_system_prompt = f"{system_prompt}\nFor general queries, provide comprehensive, detailed responses with examples and explanations where applicable."
201
 
 
202
  if len(message.split()) < 5:
203
+ enhanced_system_prompt += "\nEven for short queries, provide a detailed, in-depth response with examples and context."
204
 
205
  logger.info(f"Task type detected: {task_type}")
206
  input_messages: List[dict] = [{"role": "system", "content": enhanced_system_prompt}]
 
289
  reasoning_closed = True
290
 
291
  if not saw_visible_output:
292
+ msg = "I attempted to call a tool, but tools aren't executed in this environment."
293
  if last_tool_name:
294
  try:
295
  args_text = json.dumps(last_tool_args, ensure_ascii=False, default=str)
 
303
  cached_chunks.append(f"Error: Unknown error")
304
  yield f"Error: Unknown error"
305
  elif chunk.choices[0].finish_reason == "length":
306
+ cached_chunks.append("Response truncated due to token limit. Please refine your query.")
307
+ yield "Response truncated due to token limit. Please refine your query."
308
  break
309
 
310
  if buffer:
 
336
  ):
337
  yield chunk
338
  return
339
+ for fallback_model in [SECONDARY_MODEL_NAME, TERTIARY_MODEL_NAME]:
340
+ logger.info(f"Retrying with fallback model: {fallback_model}")
 
 
341
  try:
342
+ is_available, selected_api_key = check_model_availability(fallback_model, FALLBACK_API_ENDPOINT, selected_api_key)
343
  if not is_available:
344
+ continue
345
+ client = OpenAI(api_key=selected_api_key, base_url=FALLBACK_API_ENDPOINT, timeout=120.0)
 
346
  stream = client.chat.completions.create(
347
  model=fallback_model,
348
  messages=input_messages,
 
355
  for chunk in stream:
356
  if chunk.choices[0].delta.content:
357
  content = chunk.choices[0].delta.content
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  saw_visible_output = True
359
  buffer += content
 
360
  if "\n" in buffer or len(buffer) > 5000:
361
  cached_chunks.append(buffer)
362
  yield buffer
363
  buffer = ""
364
  continue
 
365
  if chunk.choices[0].finish_reason in ("stop", "error", "length"):
366
  if buffer:
367
  cached_chunks.append(buffer)
368
  yield buffer
369
  buffer = ""
 
 
 
 
 
 
370
  if not saw_visible_output:
371
  cached_chunks.append("No visible output produced.")
372
  yield "No visible output produced."
 
374
  cached_chunks.append(f"Error: Unknown error with fallback model {fallback_model}")
375
  yield f"Error: Unknown error with fallback model {fallback_model}"
376
  elif chunk.choices[0].finish_reason == "length":
377
+ cached_chunks.append("Response truncated due to token limit.")
378
+ yield "Response truncated due to token limit."
379
  break
 
380
  if buffer:
381
  cached_chunks.append(buffer)
382
  yield buffer
 
383
  cache[cache_key] = cached_chunks
384
+ return
385
  except Exception as e2:
386
  logger.exception(f"[Gateway] Streaming failed for fallback model {fallback_model}: {e2}")
387
+ continue
388
+ yield f"Error: Failed to load all models: Primary ({model_name}), Secondary ({SECONDARY_MODEL_NAME}), Tertiary ({TERTIARY_MODEL_NAME})."
389
+ return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
 
391
  def format_final(analysis_text: str, visible_text: str) -> str:
392
  reasoning_safe = html.escape((analysis_text or "").strip())
 
436
  "type": "function",
437
  "function": {
438
  "name": "code_generation",
439
+ "description": "Generate or modify code for various frameworks",
440
  "parameters": {
441
  "type": "object",
442
  "properties": {
utils/web_search.py CHANGED
@@ -10,31 +10,20 @@ def web_search(query: str) -> str:
10
  google_api_key = os.getenv("GOOGLE_API_KEY")
11
  google_cse_id = os.getenv("GOOGLE_CSE_ID")
12
  if not google_api_key or not google_cse_id:
13
- logger.warning("GOOGLE_API_KEY or GOOGLE_CSE_ID not set.")
14
  return "Web search requires GOOGLE_API_KEY and GOOGLE_CSE_ID to be set."
15
  url = f"https://www.googleapis.com/customsearch/v1?key={google_api_key}&cx={google_cse_id}&q={query}"
16
- response = requests.get(url, timeout=10)
17
  response.raise_for_status()
18
  results = response.json().get("items", [])
19
  if not results:
20
- logger.info(f"No web results found for query: {query}")
21
  return "No web results found."
22
  search_results = []
23
- for i, item in enumerate(results[:5]):
24
  title = item.get("title", "")
25
  snippet = item.get("snippet", "")
26
  link = item.get("link", "")
27
- try:
28
- page_response = requests.get(link, timeout=5)
29
- page_response.raise_for_status()
30
- soup = BeautifulSoup(page_response.text, "html.parser")
31
- paragraphs = soup.find_all("p")
32
- page_content = " ".join([p.get_text() for p in paragraphs][:1000])
33
- except Exception as e:
34
- logger.warning(f"Failed to fetch page content for {link}: {e}")
35
- page_content = snippet
36
- search_results.append(f"Result {i+1}:\nTitle: {title}\nLink: {link}\nContent: {page_content}\n")
37
  return "\n".join(search_results)
38
  except Exception as e:
39
- logger.exception(f"Web search failed for query: {query}")
40
  return f"Web search error: {e}"
 
10
  google_api_key = os.getenv("GOOGLE_API_KEY")
11
  google_cse_id = os.getenv("GOOGLE_CSE_ID")
12
  if not google_api_key or not google_cse_id:
 
13
  return "Web search requires GOOGLE_API_KEY and GOOGLE_CSE_ID to be set."
14
  url = f"https://www.googleapis.com/customsearch/v1?key={google_api_key}&cx={google_cse_id}&q={query}"
15
+ response = requests.get(url, timeout=5)
16
  response.raise_for_status()
17
  results = response.json().get("items", [])
18
  if not results:
 
19
  return "No web results found."
20
  search_results = []
21
+ for i, item in enumerate(results[:3]): # قللنا العدد لتسريع البحث
22
  title = item.get("title", "")
23
  snippet = item.get("snippet", "")
24
  link = item.get("link", "")
25
+ search_results.append(f"Result {i+1}:\nTitle: {title}\nLink: {link}\nContent: {snippet}\n")
 
 
 
 
 
 
 
 
 
26
  return "\n".join(search_results)
27
  except Exception as e:
28
+ logger.exception(f"Web search failed: {e}")
29
  return f"Web search error: {e}"