Spaces:
Sleeping
Sleeping
Upload 2 files
Browse files- agentic_rag.py +61 -0
- app.py +15 -2
agentic_rag.py
CHANGED
|
@@ -385,3 +385,64 @@ Raison détaillée avec référence légale."""
|
|
| 385 |
return resp.choices[0].message.content
|
| 386 |
except Exception as e:
|
| 387 |
return f"❌ خطأ: {e}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
return resp.choices[0].message.content
|
| 386 |
except Exception as e:
|
| 387 |
return f"❌ خطأ: {e}"
|
| 388 |
+
|
| 389 |
+
def generate_suggestions(self, question: str, answer: str, lang: str) -> list[str]:
|
| 390 |
+
"""يولّد 3 اقتراحات ذات صلة بعد الإجابة"""
|
| 391 |
+
if not self.llm:
|
| 392 |
+
return []
|
| 393 |
+
try:
|
| 394 |
+
if lang == "ar":
|
| 395 |
+
prompt = f"""أنت مساعد ENA تونس. بناءً على هذا السؤال والإجابة، اقترح 3 أسئلة متابعة مفيدة يمكن أن يسألها المستخدم.
|
| 396 |
+
|
| 397 |
+
السؤال: {question}
|
| 398 |
+
الإجابة: {answer[:300]}
|
| 399 |
+
|
| 400 |
+
القواعد:
|
| 401 |
+
- الأسئلة قصيرة ومباشرة (أقل من 10 كلمات)
|
| 402 |
+
- ذات صلة بموضوع المناظرات أو التكوين في ENA
|
| 403 |
+
- متنوعة (لا تكرر نفس الموضوع)
|
| 404 |
+
|
| 405 |
+
أجب بـ JSON فقط بدون أي نص آخر: ["سؤال1", "سؤال2", "سؤال3"]"""
|
| 406 |
+
else:
|
| 407 |
+
prompt = f"""Tu es l'assistant ENA Tunisie. Basé sur cette question et réponse, propose 3 questions de suivi utiles.
|
| 408 |
+
|
| 409 |
+
Question: {question}
|
| 410 |
+
Réponse: {answer[:300]}
|
| 411 |
+
|
| 412 |
+
Règles:
|
| 413 |
+
- Questions courtes et directes (moins de 10 mots)
|
| 414 |
+
- Liées aux concours ou formations ENA
|
| 415 |
+
- Variées
|
| 416 |
+
|
| 417 |
+
Réponds en JSON uniquement: ["question1", "question2", "question3"]"""
|
| 418 |
+
|
| 419 |
+
resp = self.llm.chat.completions.create(
|
| 420 |
+
model="llama-3.3-70b-versatile",
|
| 421 |
+
messages=[{"role": "user", "content": prompt}],
|
| 422 |
+
temperature=0.3,
|
| 423 |
+
max_tokens=150
|
| 424 |
+
)
|
| 425 |
+
content = resp.choices[0].message.content.strip()
|
| 426 |
+
# تنظيف الـ JSON
|
| 427 |
+
import re as _re
|
| 428 |
+
m = _re.search(r'\[.*?\]', content, _re.DOTALL)
|
| 429 |
+
if m:
|
| 430 |
+
import json as _json
|
| 431 |
+
suggestions = _json.loads(m.group())
|
| 432 |
+
return suggestions[:3]
|
| 433 |
+
except Exception:
|
| 434 |
+
pass
|
| 435 |
+
|
| 436 |
+
# اقتراحات افتراضية إذا فشل الـ LLM
|
| 437 |
+
if lang == "ar":
|
| 438 |
+
return [
|
| 439 |
+
"ما هي وثائق ملف الترشح؟",
|
| 440 |
+
"متى تفتح المناظرات القادمة؟",
|
| 441 |
+
"هل أنا مؤهل للترشح؟"
|
| 442 |
+
]
|
| 443 |
+
else:
|
| 444 |
+
return [
|
| 445 |
+
"Quels documents pour le dossier?",
|
| 446 |
+
"Quand ouvrent les prochains concours?",
|
| 447 |
+
"Suis-je éligible?"
|
| 448 |
+
]
|
app.py
CHANGED
|
@@ -73,8 +73,7 @@ def render_sidebar():
|
|
| 73 |
try:
|
| 74 |
db_count = 0
|
| 75 |
try:
|
| 76 |
-
|
| 77 |
-
db_count = len(col_data.get("documents", []))
|
| 78 |
except:
|
| 79 |
# If collection get fails, we treat it as empty
|
| 80 |
db_count = 0
|
|
@@ -206,6 +205,20 @@ def main():
|
|
| 206 |
st.session_state.waiting_for_profile = True
|
| 207 |
|
| 208 |
st.markdown(full_response)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
st.session_state.chat_messages.append({"role": "assistant", "content": full_response})
|
| 210 |
st.session_state.last_tool = tool_used
|
| 211 |
st.session_state.last_sources = result.get("sources", [])
|
|
|
|
| 73 |
try:
|
| 74 |
db_count = 0
|
| 75 |
try:
|
| 76 |
+
db_count = engine.vectordb._collection.count()
|
|
|
|
| 77 |
except:
|
| 78 |
# If collection get fails, we treat it as empty
|
| 79 |
db_count = 0
|
|
|
|
| 205 |
st.session_state.waiting_for_profile = True
|
| 206 |
|
| 207 |
st.markdown(full_response)
|
| 208 |
+
|
| 209 |
+
# ══ اقتراحات ذكية بعد الإجابة (مثل Claude/ChatGPT) ══
|
| 210 |
+
if tool_used not in ("chat", "ask_user_profile") and not result.get("needs_user_input"):
|
| 211 |
+
with st.spinner("💡 ..."):
|
| 212 |
+
suggestions = agent.generate_suggestions(final_q, full_response, lang)
|
| 213 |
+
if suggestions:
|
| 214 |
+
sug_label = "💡 قد يهمك أيضاً:" if lang == "ar" else "💡 Vous pourriez aussi demander:"
|
| 215 |
+
st.markdown(f"---\n**{sug_label}**")
|
| 216 |
+
sug_cols = st.columns(len(suggestions))
|
| 217 |
+
for i, sug in enumerate(suggestions):
|
| 218 |
+
if sug_cols[i].button(sug, key=f"sug_{len(st.session_state.chat_messages)}_{i}"):
|
| 219 |
+
st.session_state.pending_q = sug
|
| 220 |
+
st.rerun()
|
| 221 |
+
|
| 222 |
st.session_state.chat_messages.append({"role": "assistant", "content": full_response})
|
| 223 |
st.session_state.last_tool = tool_used
|
| 224 |
st.session_state.last_sources = result.get("sources", [])
|