Marylene commited on
Commit
9ff51f8
·
verified ·
1 Parent(s): 133e275

Mise en forme

Browse files
Files changed (1) hide show
  1. app.py +94 -16
app.py CHANGED
@@ -6,6 +6,36 @@ from contextlib import redirect_stdout, redirect_stderr
6
  # 👉 adapte le module si ton fichier agent a un autre nom
7
  from quick_deploy_agent import build_agent, parse_result
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  # ---------- util "tee" pour dupliquer les logs ----------
10
  class _Tee(io.TextIOBase):
11
  def __init__(self, *streams): self.streams = streams
@@ -25,56 +55,104 @@ if hasattr(agent, "verbosity_level"):
25
  agent.verbosity_level = 3 # 0..3 (3 = très verbeux)
26
 
27
  # ---------- prompt interne (l'utilisateur ne tape rien) ----------
28
- TASK_TMPL = """
29
  Classe ce produit en COICOP:
30
  EAN: {ean}
31
  Libellé: {label}
32
- Pipeline:
 
 
 
 
 
33
  1) validate_ean(ean)
34
  2) openfoodfacts_product_by_ean(ean)
35
- 3) map_off_to_coicop(off_payload=<retour brut de l'étape 2>)
36
- (ou, si nécessaire, map_off_to_coicop(product_name, categories_tags, ingredients_text))
37
- 4) coicop_regex_rules(text=libellé)
38
- 5) coicop_semantic_similarity(text=libellé, topk=5)
39
  6) resolve_coicop_candidates([...], topn=3)
40
- Contraintes:
41
- - Si libellé vide mais EAN présent, utilise le product_name issu de (2) comme texte pour (4) et (5).
42
- - Termine après (6) et retourne uniquement le JSON final.
 
 
 
43
  """
44
 
 
45
  def classify(label: str, ean: str):
 
46
  label = (label or "").strip()
47
  ean = (ean or "").strip()
 
48
  if not label and not ean:
49
  return json.dumps({"error": "Veuillez saisir un libellé ou un EAN."}, ensure_ascii=False, indent=2), "—"
50
 
 
51
  task = TASK_TMPL.format(ean=ean or "N/A", label=label or "N/A")
52
 
53
- # Buffers UI + duplication vers logs Space
54
  buf_out, buf_err = io.StringIO(), io.StringIO()
55
  tee_out, tee_err = _Tee(sys.stdout, buf_out), _Tee(sys.stderr, buf_err)
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  print("\n===== Agent run start =====")
58
  print(task)
59
 
60
  try:
 
61
  with redirect_stdout(tee_out), redirect_stderr(tee_err):
62
  res = agent.run(task)
63
 
64
- try:
65
- obj = parse_result(res)
66
- except Exception:
67
- obj = {"raw": res}
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
- logs_ui = "\n".join(s for s in [buf_out.getvalue().strip(), buf_err.getvalue().strip()] if s) or "(aucun log)"
70
  print("===== Agent run end =====\n")
71
  return json.dumps(obj, ensure_ascii=False, indent=2), logs_ui
72
 
73
  except Exception as e:
74
- logs_ui = "\n".join(s for s in [buf_out.getvalue().strip(), buf_err.getvalue().strip()] if s) or "(aucun log)"
 
 
75
  print(f"✖ Agent error: {e}")
76
  return json.dumps({"error": str(e)}, ensure_ascii=False, indent=2), logs_ui
77
 
 
78
  def fill_example():
79
  # EAN réel OFF (Les p'tits crémeux – Aldi – 216 g)
80
  return "Camembert au lait cru AOP 250g - ALDI", "2006050033638"
 
6
  # 👉 adapte le module si ton fichier agent a un autre nom
7
  from quick_deploy_agent import build_agent, parse_result
8
 
9
+
10
+ # --- en haut de app.py, ajoute cet util ---
11
+ import re
12
+
13
+ def _extract_json(text: str):
14
+ """
15
+ Essaie de parser un JSON valable depuis une chaîne.
16
+ - d'abord json.loads direct
17
+ - sinon, extrait le contenu d'un bloc ```json ... ``` puis parse
18
+ - renvoie None si rien de valable
19
+ """
20
+ if isinstance(text, dict):
21
+ return text
22
+ if not isinstance(text, str):
23
+ return None
24
+ # 1) direct
25
+ try:
26
+ return json.loads(text)
27
+ except Exception:
28
+ pass
29
+ # 2) dans des backticks
30
+ try:
31
+ m = re.search(r"```(?:json)?\s*(\{[\s\S]*\})\s*```", text)
32
+ if m:
33
+ return json.loads(m.group(1))
34
+ except Exception:
35
+ pass
36
+ return None
37
+
38
+
39
  # ---------- util "tee" pour dupliquer les logs ----------
40
  class _Tee(io.TextIOBase):
41
  def __init__(self, *streams): self.streams = streams
 
55
  agent.verbosity_level = 3 # 0..3 (3 = très verbeux)
56
 
57
  # ---------- prompt interne (l'utilisateur ne tape rien) ----------
58
+ TASK_TMPL = """\
59
  Classe ce produit en COICOP:
60
  EAN: {ean}
61
  Libellé: {label}
62
+
63
+ Outils autorisés UNIQUEMENT : validate_ean, openfoodfacts_product_by_ean,
64
+ map_off_to_coicop, coicop_regex_rules, coicop_semantic_similarity, resolve_coicop_candidates.
65
+ N'UTILISE PAS python_interpreter. N'ÉCRIS PAS DE CODE. N'INDEXE JAMAIS la sortie d'un tool.
66
+
67
+ Pipeline :
68
  1) validate_ean(ean)
69
  2) openfoodfacts_product_by_ean(ean)
70
+ 3) map_off_to_coicop(off_payload=<sortie brute de (2)>) ou, si nécessaire,
71
+ map_off_to_coicop(product_name, categories_tags, ingredients_text)
72
+ 4) coicop_regex_rules(text=LIBELLÉ UTILISATEUR)
73
+ 5) coicop_semantic_similarity(text=LIBELLÉ UTILISATEUR, topk=5)
74
  6) resolve_coicop_candidates([...], topn=3)
75
+
76
+ Règles strictes :
77
+ - Pour (4) et (5), UTILISE EXCLUSIVEMENT le libellé fourni par l'utilisateur (ne lis pas la réponse OFF).
78
+ - Si le libellé est vide, effectue (4) et (5) avec une chaîne vide (pas d'indexation JSON).
79
+ Sortie :
80
+ - Retourne UNIQUEMENT un objet JSON valide, sans backticks, sans prose.
81
  """
82
 
83
+
84
  def classify(label: str, ean: str):
85
+ import re
86
  label = (label or "").strip()
87
  ean = (ean or "").strip()
88
+
89
  if not label and not ean:
90
  return json.dumps({"error": "Veuillez saisir un libellé ou un EAN."}, ensure_ascii=False, indent=2), "—"
91
 
92
+ # Construire le message pour l’agent
93
  task = TASK_TMPL.format(ean=ean or "N/A", label=label or "N/A")
94
 
95
+ # Buffers UI + duplication vers logs Space (stdout/stderr)
96
  buf_out, buf_err = io.StringIO(), io.StringIO()
97
  tee_out, tee_err = _Tee(sys.stdout, buf_out), _Tee(sys.stderr, buf_err)
98
 
99
+ # Utilitaire local : extraire un vrai JSON d'une chaîne (gère les ```json ... ```)
100
+ def _extract_json(text: str):
101
+ if isinstance(text, dict):
102
+ return text
103
+ if not isinstance(text, str):
104
+ return None
105
+ # 1) direct
106
+ try:
107
+ return json.loads(text)
108
+ except Exception:
109
+ pass
110
+ # 2) JSON dans des backticks
111
+ try:
112
+ m = re.search(r"```(?:json)?\s*(\{[\s\S]*\})\s*```", text)
113
+ if m:
114
+ return json.loads(m.group(1))
115
+ except Exception:
116
+ pass
117
+ return None
118
+
119
  print("\n===== Agent run start =====")
120
  print(task)
121
 
122
  try:
123
+ # Exécuter l’agent en dupliquant les logs vers l’UI et le Space
124
  with redirect_stdout(tee_out), redirect_stderr(tee_err):
125
  res = agent.run(task)
126
 
127
+ # Parsing robuste du résultat
128
+ obj = None
129
+ if isinstance(res, dict):
130
+ obj = res
131
+ if obj is None:
132
+ obj = _extract_json(res)
133
+ if obj is None:
134
+ try:
135
+ obj = parse_result(res) # ton parseur existant
136
+ except Exception:
137
+ obj = None
138
+ if obj is None:
139
+ obj = {"raw": str(res)} # dernier recours
140
+
141
+ logs_ui = "\n".join(
142
+ s for s in [buf_out.getvalue().strip(), buf_err.getvalue().strip()] if s
143
+ ) or "(aucun log)"
144
 
 
145
  print("===== Agent run end =====\n")
146
  return json.dumps(obj, ensure_ascii=False, indent=2), logs_ui
147
 
148
  except Exception as e:
149
+ logs_ui = "\n".join(
150
+ s for s in [buf_out.getvalue().strip(), buf_err.getvalue().strip()] if s
151
+ ) or "(aucun log)"
152
  print(f"✖ Agent error: {e}")
153
  return json.dumps({"error": str(e)}, ensure_ascii=False, indent=2), logs_ui
154
 
155
+
156
  def fill_example():
157
  # EAN réel OFF (Les p'tits crémeux – Aldi – 216 g)
158
  return "Camembert au lait cru AOP 250g - ALDI", "2006050033638"