klydekushy commited on
Commit
1ca0f8c
·
verified ·
1 Parent(s): 2d5df05

Update src/modules/jasmine_agent.py

Browse files
Files changed (1) hide show
  1. src/modules/jasmine_agent.py +72 -29
src/modules/jasmine_agent.py CHANGED
@@ -1,7 +1,8 @@
1
  """
2
- MODULE JASMINE AGENT - V27 SPARQL EDITION
3
- =========================================
4
- L'agent utilise maintenant un vrai raisonnement sémantique et structuré.
 
5
  """
6
  import google.generativeai as genai
7
  from groq import Groq
@@ -11,8 +12,12 @@ import os
11
  import re
12
 
13
  class JasmineAgent:
14
- def __init__(self, rdf_store):
15
- # ... (Config API Keys identique à avant) ...
 
 
 
 
16
  self.google_key = os.environ.get("GOOGLE_API_KEY")
17
  if not self.google_key and "GOOGLE_API_KEY" in st.secrets:
18
  self.google_key = st.secrets["GOOGLE_API_KEY"]
@@ -25,51 +30,91 @@ class JasmineAgent:
25
  self.groq_client = Groq(api_key=self.groq_key) if self.groq_key else None
26
 
27
  self.rdf_store = rdf_store
28
- self.available_predicates = rdf_store.get_schema() if rdf_store else ""
29
-
30
  # Cascade de modèles
 
31
  self.MODEL_CASCADE = [
32
 
 
 
33
  "gemini-2.0-flash-exp",
 
34
  "llama-3.3-70b-versatile",
 
35
  "gemini-2.0-flash-lite",
 
36
  "llama-3.1-8b-instant"
37
 
 
 
38
  ]
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  def build_system_prompt(self):
41
- # NOTE : Les doubles accolades {{ }} sont pour le f-string Python
 
42
  return f"""
43
- Tu es JASMINE, un AGENT DATA INTELLIGENT. Tu interroges un Knowledge Graph via SPARQL.
 
 
44
 
45
- 🗺️ CARTOGRAPHIE DES DONNÉES (RÉALITÉ DU TERRAIN) :
46
  PREFIX: vortex: <http://vortex.ai/ontology#>
47
- {self.available_predicates}
48
 
49
- ⚠️ RÈGLE D'OR SPARQL :
50
- 1. Les prédicats sont EXACTS (sensible à la casse). Si la liste ci-dessus dit `vortex:Ville`, N'UTILISE PAS `vortex:habite`.
51
- 2. Pour les valeurs (Objets), utilise souvent `FILTER(CONTAINS(?variable, "Texte"))` car les URIs peuvent être complexes.
52
- 3. Ne mets pas de guillemets autour des URIs dans le WHERE, sauf si ce sont des chaînes (Literal).
 
 
 
 
 
 
53
 
54
  🛠️ TES OUTILS :
55
 
56
- 1️⃣ 🧠 RECHERCHE SÉMANTIQUE (FAISS)
57
  {{"tool": "search_semantic", "args": {{"query": "..."}}}}
58
- → Pour trouver comment s'écrit une entité (ex: est-ce `vortex:DAKAR` ou `vortex:Ville_Dakar` ?).
59
 
60
  2️⃣ ⚡ REQUÊTE SPARQL
61
- {{"tool": "execute_sparql", "args": {{"query": "SELECT ?s ?p ?o WHERE ..."}}}}
62
- → Pour récupérer les données.
63
- → ASTUCE : Si tu ne connais pas le prédicat, fais : `SELECT ?p ?o WHERE {{ vortex:ID_TROUVÉ ?p ?o }}` pour inspecter l'entité.
64
-
65
- Exemple Workflow Correct :
66
- User: "Cherche un commercial"
67
- Toi: {{"tool": "search_semantic", "args": {{"query": "Commercial"}}}}
68
- [Système: vortex:Metier_Commercial]
69
- Toi: {{"tool": "execute_sparql", "args": {{"query": "SELECT ?s WHERE {{ ?s vortex:Profession ?o . FILTER(CONTAINS(?o, 'Commercial')) }}"}}}}
70
 
 
71
  """
72
-
 
73
  def _format_messages_for_groq(self, system_prompt, chat_history, user_message):
74
  msgs = [{"role": "system", "content": system_prompt}]
75
  for m in chat_history[-8:]:
@@ -123,7 +168,6 @@ Toi: {{"tool": "execute_sparql", "args": {{"query": "SELECT ?s WHERE {{ ?s vorte
123
  )
124
  response_text = completion.choices[0].message.content
125
 
126
- # Extraction de l'action JSON
127
  clean_text = re.sub(r"```json", "", response_text, flags=re.IGNORECASE)
128
  clean_text = re.sub(r"```", "", clean_text).strip()
129
 
@@ -134,7 +178,6 @@ Toi: {{"tool": "execute_sparql", "args": {{"query": "SELECT ?s WHERE {{ ?s vorte
134
  json_end = clean_text.rfind("}") + 1
135
  json_str = clean_text[json_start:json_end]
136
  action = json.loads(json_str)
137
- # On retire le JSON du texte si présent pour l'affichage
138
  clean_text = clean_text[:json_start] + clean_text[json_end:]
139
  clean_text = clean_text.strip()
140
  except:
 
1
  """
2
+ MODULE JASMINE AGENT - V29 DYNAMIC ONTOLOGY
3
+ ===========================================
4
+ L'agent lit dynamiquement la structure depuis la feuille Excel 'Ontology'.
5
+ Plus besoin de hardcoder le schéma.
6
  """
7
  import google.generativeai as genai
8
  from groq import Groq
 
12
  import re
13
 
14
  class JasmineAgent:
15
+ def __init__(self, rdf_store, ontology_rules):
16
+ """
17
+ :param rdf_store: L'objet d'accès à la base RDF
18
+ :param ontology_rules: Liste de dicts représentant la feuille Excel 'Ontology'
19
+ """
20
+ # Config APIs
21
  self.google_key = os.environ.get("GOOGLE_API_KEY")
22
  if not self.google_key and "GOOGLE_API_KEY" in st.secrets:
23
  self.google_key = st.secrets["GOOGLE_API_KEY"]
 
30
  self.groq_client = Groq(api_key=self.groq_key) if self.groq_key else None
31
 
32
  self.rdf_store = rdf_store
33
+ self.ontology_rules = ontology_rules
34
+
35
  # Cascade de modèles
36
+
37
  self.MODEL_CASCADE = [
38
 
39
+
40
+
41
  "gemini-2.0-flash-exp",
42
+
43
  "llama-3.3-70b-versatile",
44
+
45
  "gemini-2.0-flash-lite",
46
+
47
  "llama-3.1-8b-instant"
48
 
49
+
50
+
51
  ]
52
 
53
+ def _generate_dynamic_schema_prompt(self):
54
+ """Transforme les règles Excel en instructions SPARQL claires"""
55
+ schema_desc = ""
56
+ relations = []
57
+ properties = []
58
+
59
+ for rule in self.ontology_rules:
60
+ subject = rule.get('SubjectClass', 'Entity')
61
+ predicate = rule.get('Predicate', 'unknown')
62
+ obj = rule.get('ObjectColOrConcept', 'unknown')
63
+ obj_type = rule.get('ObjectType', 'data_property')
64
+
65
+ # Nettoyage du nom du prédicat (gestion des prefixes)
66
+ pred_clean = predicate if ":" in predicate else f"vortex:{predicate}"
67
+
68
+ if obj_type == 'relation':
69
+ relations.append(f"- {subject} --[{pred_clean}]--> {obj} (URI)")
70
+ else:
71
+ properties.append(f"- {subject} a la propriété '{pred_clean}' ({obj})")
72
+
73
+ return f"""
74
+ 🗺️ CARTOGRAPHIE DYNAMIQUE (Générée depuis ta feuille Ontology) :
75
+
76
+ A. RELATIONS (Liens entre entités - URIs) :
77
+ *(Utilise FILTER(CONTAINS(STR(?o), 'valeur')) pour chercher dedans)*
78
+ {chr(10).join(sorted(relations))}
79
+
80
+ B. PROPRIÉTÉS (Texte/Nombres - Literals) :
81
+ *(Utilise FILTER(?o = 'valeur') ou CONTAINS)*
82
+ {chr(10).join(sorted(properties))}
83
+ """
84
+
85
  def build_system_prompt(self):
86
+ dynamic_schema = self._generate_dynamic_schema_prompt()
87
+
88
  return f"""
89
+ Tu es JASMINE, un Agent Data. Tu convertis le Langage Naturel en SPARQL en respectant STRICTEMENT le schéma ci-dessous.
90
+
91
+ {dynamic_schema}
92
 
 
93
  PREFIX: vortex: <http://vortex.ai/ontology#>
 
94
 
95
+ ⚠️ RÈGLES CRITIQUES DE SYNTAXE SPARQL :
96
+ 1. **RELATIONS vs PROPRIÉTÉS** :
97
+ - Regarde la liste ci-dessus. Si c'est une RELATION (URI), tu NE PEUX PAS faire `?s predicate 'String'`.
98
+ - Tu dois faire : `?s predicate ?o . FILTER(CONTAINS(STR(?o), 'Dakar'))`
99
+
100
+ 2. **NOMS EXACTS** : N'invente aucun prédicat. Utilise uniquement ceux listés dans la Cartographie (ex: `vortex:habite_ville`, `vortex:secteur_act`).
101
+
102
+ 3. **WORKFLOW** :
103
+ - Etape 1 : `search_semantic` pour trouver comment s'écrivent les choses (ex: est-ce 'Commercial' ou 'Vente' ?).
104
+ - Etape 2 : `execute_sparql` pour récupérer les IDs et les infos.
105
 
106
  🛠️ TES OUTILS :
107
 
108
+ 1️⃣ 🧠 RECHERCHE SÉMANTIQUE
109
  {{"tool": "search_semantic", "args": {{"query": "..."}}}}
 
110
 
111
  2️⃣ ⚡ REQUÊTE SPARQL
112
+ {{"tool": "execute_sparql", "args": {{"query": "SELECT ?nom ?ville WHERE {{ ?s vortex:secteur_act ?secteur . FILTER(CONTAINS(STR(?secteur), 'Commercial')) . ?s vortex:habite_ville ?ville . FILTER(CONTAINS(STR(?ville), 'Dakar')) . ?s vortex:nom ?nom }}"}}}}
 
 
 
 
 
 
 
 
113
 
114
+ ⚠️ ACTION UNIQUE : Un seul JSON par réponse. STOP après le JSON.
115
  """
116
+
117
+ # ... (Les méthodes _format_messages_... et ask restent inchangées)
118
  def _format_messages_for_groq(self, system_prompt, chat_history, user_message):
119
  msgs = [{"role": "system", "content": system_prompt}]
120
  for m in chat_history[-8:]:
 
168
  )
169
  response_text = completion.choices[0].message.content
170
 
 
171
  clean_text = re.sub(r"```json", "", response_text, flags=re.IGNORECASE)
172
  clean_text = re.sub(r"```", "", clean_text).strip()
173
 
 
178
  json_end = clean_text.rfind("}") + 1
179
  json_str = clean_text[json_start:json_end]
180
  action = json.loads(json_str)
 
181
  clean_text = clean_text[:json_start] + clean_text[json_end:]
182
  clean_text = clean_text.strip()
183
  except: