Spaces:
Running
Running
Update src/modules/jasmine_agent.py
Browse files- src/modules/jasmine_agent.py +44 -47
src/modules/jasmine_agent.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
"""
|
| 2 |
-
MODULE JASMINE AGENT -
|
| 3 |
-
==========================================
|
| 4 |
-
|
| 5 |
-
|
| 6 |
"""
|
| 7 |
import google.generativeai as genai
|
| 8 |
from groq import Groq
|
|
@@ -29,11 +29,8 @@ class JasmineAgent:
|
|
| 29 |
self.rdf_store = rdf_store
|
| 30 |
self.ontology_rules = ontology_rules
|
| 31 |
|
| 32 |
-
#
|
| 33 |
-
# Si Gemini échoue, on passe à Llama 70b (plus robuste que 8b)
|
| 34 |
-
# Cascade de modèles
|
| 35 |
self.MODEL_CASCADE = [
|
| 36 |
-
|
| 37 |
"gemini-2.0-flash-exp",
|
| 38 |
"llama-3.3-70b-versatile",
|
| 39 |
"gemini-2.0-flash-lite",
|
|
@@ -41,7 +38,7 @@ class JasmineAgent:
|
|
| 41 |
]
|
| 42 |
|
| 43 |
def _generate_dynamic_schema_prompt(self):
|
| 44 |
-
"""
|
| 45 |
schema_map = defaultdict(lambda: {"relations": [], "props": []})
|
| 46 |
|
| 47 |
for rule in self.ontology_rules:
|
|
@@ -50,52 +47,53 @@ class JasmineAgent:
|
|
| 50 |
obj = rule.get('ObjectColOrConcept', 'unknown')
|
| 51 |
obj_type = rule.get('ObjectType', 'data_property')
|
| 52 |
|
| 53 |
-
# Nettoyage
|
| 54 |
pred_clean = predicate if ":" in predicate else f"vortex:{predicate}"
|
| 55 |
|
| 56 |
if obj_type == 'relation':
|
| 57 |
-
schema_map[subject]["relations"].append(f"-> {pred_clean}
|
| 58 |
else:
|
| 59 |
schema_map[subject]["props"].append(f"{pred_clean}")
|
| 60 |
|
| 61 |
-
#
|
| 62 |
-
|
| 63 |
for cls, data in schema_map.items():
|
| 64 |
-
lines
|
| 65 |
-
if data["relations"]:
|
| 66 |
-
|
| 67 |
-
if data["props"]:
|
| 68 |
-
lines.append(f" 📝 PROPS: {', '.join(data['props'])}")
|
| 69 |
-
compact_text.append("\n".join(lines))
|
| 70 |
|
| 71 |
-
return "\n".join(
|
| 72 |
|
| 73 |
def build_system_prompt(self):
|
| 74 |
dynamic_schema = self._generate_dynamic_schema_prompt()
|
| 75 |
|
| 76 |
return f"""
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
PREFIX: vortex: <http://vortex.ai/ontology#>
|
| 80 |
|
|
|
|
|
|
|
| 81 |
{dynamic_schema}
|
| 82 |
|
| 83 |
-
⚠️ RÈGLES SPARQL :
|
| 84 |
-
1.
|
| 85 |
-
2.
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
2️⃣ {{ "tool": "execute_sparql", "args": {{ "query": "SELECT..." }} }}
|
| 91 |
|
| 92 |
-
|
| 93 |
"""
|
| 94 |
|
| 95 |
def _format_messages_for_groq(self, system_prompt, chat_history, user_message):
|
| 96 |
msgs = [{"role": "system", "content": system_prompt}]
|
| 97 |
-
#
|
| 98 |
-
for m in chat_history[-
|
| 99 |
role = "assistant" if m["role"] in ["model", "assistant"] else "user"
|
| 100 |
content = str(m.get("content", ""))
|
| 101 |
if content.strip():
|
|
@@ -105,7 +103,7 @@ ACTION UNIQUE. STOP APRÈS JSON.
|
|
| 105 |
|
| 106 |
def _format_messages_for_gemini(self, system_prompt, chat_history, user_message):
|
| 107 |
msgs = [{"role": "user", "parts": [system_prompt]}]
|
| 108 |
-
for m in chat_history[-
|
| 109 |
role = "user" if m["role"] == "user" else "model"
|
| 110 |
content = str(m.get("content", ""))
|
| 111 |
if content.strip():
|
|
@@ -121,13 +119,13 @@ ACTION UNIQUE. STOP APRÈS JSON.
|
|
| 121 |
try:
|
| 122 |
response_text = ""
|
| 123 |
if "gemini" in model_name:
|
| 124 |
-
if not self.google_key: continue
|
| 125 |
formatted_msgs = self._format_messages_for_gemini(system_prompt, chat_history, user_message)
|
| 126 |
model = genai.GenerativeModel(model_name)
|
| 127 |
res = model.generate_content(formatted_msgs)
|
| 128 |
response_text = res.text
|
| 129 |
else:
|
| 130 |
-
if not self.groq_client: continue
|
| 131 |
formatted_msgs = self._format_messages_for_groq(system_prompt, chat_history, user_message)
|
| 132 |
completion = self.groq_client.chat.completions.create(
|
| 133 |
model=model_name,
|
|
@@ -136,24 +134,23 @@ ACTION UNIQUE. STOP APRÈS JSON.
|
|
| 136 |
)
|
| 137 |
response_text = completion.choices[0].message.content
|
| 138 |
|
| 139 |
-
|
| 140 |
-
clean_text =
|
|
|
|
| 141 |
|
| 142 |
action = None
|
| 143 |
-
if
|
| 144 |
try:
|
| 145 |
-
|
| 146 |
-
json_end = clean_text.rfind("}") + 1
|
| 147 |
-
json_str = clean_text[json_start:json_end]
|
| 148 |
action = json.loads(json_str)
|
| 149 |
-
|
| 150 |
-
clean_text = clean_text.strip()
|
| 151 |
except: pass
|
| 152 |
|
| 153 |
-
|
|
|
|
| 154 |
|
| 155 |
except Exception as e:
|
| 156 |
last_error = str(e)
|
| 157 |
-
continue
|
| 158 |
|
| 159 |
-
return f"⚠️ ERREUR
|
|
|
|
| 1 |
"""
|
| 2 |
+
MODULE JASMINE AGENT - V30 SILENT EXECUTOR
|
| 3 |
+
==========================================
|
| 4 |
+
Objectif : Forcer le modèle à agir (JSON) sans discuter.
|
| 5 |
+
Modif : Prompt "Headless", Compression Tokens, SPARQL Safe Mode.
|
| 6 |
"""
|
| 7 |
import google.generativeai as genai
|
| 8 |
from groq import Groq
|
|
|
|
| 29 |
self.rdf_store = rdf_store
|
| 30 |
self.ontology_rules = ontology_rules
|
| 31 |
|
| 32 |
+
# PRIORITÉ MODÈLE : Gemini (Context Window large) > Llama
|
|
|
|
|
|
|
| 33 |
self.MODEL_CASCADE = [
|
|
|
|
| 34 |
"gemini-2.0-flash-exp",
|
| 35 |
"llama-3.3-70b-versatile",
|
| 36 |
"gemini-2.0-flash-lite",
|
|
|
|
| 38 |
]
|
| 39 |
|
| 40 |
def _generate_dynamic_schema_prompt(self):
|
| 41 |
+
"""Compression du schéma pour économiser les tokens"""
|
| 42 |
schema_map = defaultdict(lambda: {"relations": [], "props": []})
|
| 43 |
|
| 44 |
for rule in self.ontology_rules:
|
|
|
|
| 47 |
obj = rule.get('ObjectColOrConcept', 'unknown')
|
| 48 |
obj_type = rule.get('ObjectType', 'data_property')
|
| 49 |
|
| 50 |
+
# Nettoyage nom prédicat
|
| 51 |
pred_clean = predicate if ":" in predicate else f"vortex:{predicate}"
|
| 52 |
|
| 53 |
if obj_type == 'relation':
|
| 54 |
+
schema_map[subject]["relations"].append(f"-> {pred_clean}")
|
| 55 |
else:
|
| 56 |
schema_map[subject]["props"].append(f"{pred_clean}")
|
| 57 |
|
| 58 |
+
# Texte compact
|
| 59 |
+
lines = []
|
| 60 |
for cls, data in schema_map.items():
|
| 61 |
+
lines.append(f"📦 {cls}")
|
| 62 |
+
if data["relations"]: lines.append(f" 🔗 {', '.join(data['relations'])}")
|
| 63 |
+
if data["props"]: lines.append(f" 📝 {', '.join(data['props'])}")
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
+
return "\n".join(lines)
|
| 66 |
|
| 67 |
def build_system_prompt(self):
|
| 68 |
dynamic_schema = self._generate_dynamic_schema_prompt()
|
| 69 |
|
| 70 |
return f"""
|
| 71 |
+
ROLE: MOTEUR D'EXÉCUTION HEADLESS (JSON ONLY).
|
| 72 |
+
TU N'ES PAS UN CHATBOT. TU NE PARLES PAS. TU EXÉCUTES.
|
|
|
|
| 73 |
|
| 74 |
+
CONTEXTE RDF (ONTOLOGIE) :
|
| 75 |
+
PREFIX: vortex: <http://vortex.ai/ontology#>
|
| 76 |
{dynamic_schema}
|
| 77 |
|
| 78 |
+
⚠️ RÈGLES SPARQL DE SURVIE :
|
| 79 |
+
1. **FILTER SAFE** : Utilise TOUJOURS `FILTER(CONTAINS(STR(?var), 'Valeur'))` au lieu de `?var = 'Valeur'`. Cela évite les erreurs de type (URI vs String).
|
| 80 |
+
2. **NOMS EXACTS** : Utilise uniquement les prédicats listés ci-dessus (ex: `vortex:habite_ville`).
|
| 81 |
+
|
| 82 |
+
PROTOCOLE DE RÉPONSE :
|
| 83 |
+
1. Si tu dois chercher une info -> Renvoie JSON `execute_sparql`.
|
| 84 |
+
2. Si tu dois trouver une URI -> Renvoie JSON `search_semantic`.
|
| 85 |
+
3. Si tu as fini -> Réponds en texte simple (courtememnt).
|
| 86 |
|
| 87 |
+
FORMAT ATTENDU (Sortie STRICTE) :
|
| 88 |
+
{{"tool": "execute_sparql", "args": {{"query": "SELECT ?s WHERE {{ ?s vortex:habite_ville ?v . FILTER(CONTAINS(STR(?v), 'Dakar')) }}"}}}}
|
|
|
|
| 89 |
|
| 90 |
+
INTERDIT D'ÉCRIRE DU TEXTE AVANT OU APRÈS LE JSON.
|
| 91 |
"""
|
| 92 |
|
| 93 |
def _format_messages_for_groq(self, system_prompt, chat_history, user_message):
|
| 94 |
msgs = [{"role": "system", "content": system_prompt}]
|
| 95 |
+
# On garde peu d'historique pour éviter le débordement de tokens
|
| 96 |
+
for m in chat_history[-3:]:
|
| 97 |
role = "assistant" if m["role"] in ["model", "assistant"] else "user"
|
| 98 |
content = str(m.get("content", ""))
|
| 99 |
if content.strip():
|
|
|
|
| 103 |
|
| 104 |
def _format_messages_for_gemini(self, system_prompt, chat_history, user_message):
|
| 105 |
msgs = [{"role": "user", "parts": [system_prompt]}]
|
| 106 |
+
for m in chat_history[-5:]:
|
| 107 |
role = "user" if m["role"] == "user" else "model"
|
| 108 |
content = str(m.get("content", ""))
|
| 109 |
if content.strip():
|
|
|
|
| 119 |
try:
|
| 120 |
response_text = ""
|
| 121 |
if "gemini" in model_name:
|
| 122 |
+
if not self.google_key: continue
|
| 123 |
formatted_msgs = self._format_messages_for_gemini(system_prompt, chat_history, user_message)
|
| 124 |
model = genai.GenerativeModel(model_name)
|
| 125 |
res = model.generate_content(formatted_msgs)
|
| 126 |
response_text = res.text
|
| 127 |
else:
|
| 128 |
+
if not self.groq_client: continue
|
| 129 |
formatted_msgs = self._format_messages_for_groq(system_prompt, chat_history, user_message)
|
| 130 |
completion = self.groq_client.chat.completions.create(
|
| 131 |
model=model_name,
|
|
|
|
| 134 |
)
|
| 135 |
response_text = completion.choices[0].message.content
|
| 136 |
|
| 137 |
+
# Nettoyage agressif pour trouver le JSON
|
| 138 |
+
clean_text = response_text.strip()
|
| 139 |
+
match = re.search(r'(\{.*"tool":.*\})', clean_text, re.DOTALL)
|
| 140 |
|
| 141 |
action = None
|
| 142 |
+
if match:
|
| 143 |
try:
|
| 144 |
+
json_str = match.group(1)
|
|
|
|
|
|
|
| 145 |
action = json.loads(json_str)
|
| 146 |
+
return "", action # On retourne une chaîne vide pour le texte, car c'est une action pure
|
|
|
|
| 147 |
except: pass
|
| 148 |
|
| 149 |
+
# Si pas de JSON trouvé, c'est une réponse texte
|
| 150 |
+
return clean_text, None
|
| 151 |
|
| 152 |
except Exception as e:
|
| 153 |
last_error = str(e)
|
| 154 |
+
continue
|
| 155 |
|
| 156 |
+
return f"⚠️ ERREUR SYSTEME : {last_error}", None
|